aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/images/icons/icon.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/atoms/images/icons/icon.tsx')
-rw-r--r--src/components/atoms/images/icons/icon.tsx126
1 files changed, 126 insertions, 0 deletions
diff --git a/src/components/atoms/images/icons/icon.tsx b/src/components/atoms/images/icons/icon.tsx
new file mode 100644
index 0000000..23170d9
--- /dev/null
+++ b/src/components/atoms/images/icons/icon.tsx
@@ -0,0 +1,126 @@
+import type { SVGAttributes } from 'react';
+import { HamburgerIcon, type HamburgerIconProps } from './hamburger-icon';
+import styles from './icon.module.scss';
+import {
+ PlusMinusIcon,
+ type PlusMinusIconProps,
+ type PlusMinusIconShape,
+} from './plus-minus-icon';
+import { type SVGIconShape, SVGPaths, type SVGPathsProps } from './svg-paths';
+
+export type IconShape = SVGIconShape | PlusMinusIconShape | 'hamburger';
+
+export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+
+type SVGIconProps = Omit<
+ SVGAttributes<SVGElement>,
+ 'children' | 'viewBox' | 'xmlns'
+> & {
+ /**
+ * Describe the icon.
+ */
+ description?: string;
+ /**
+ * Define an accessible title for the icon.
+ */
+ heading?: string;
+};
+
+type IconBaseProps<T extends IconShape> = T extends 'hamburger'
+ ? HamburgerIconProps
+ : T extends 'minus' | 'plus'
+ ? PlusMinusIconProps
+ : SVGIconProps;
+
+type AdditionalProps<T extends IconShape> = Pick<
+ SVGPathsProps,
+ 'orientation'
+> & {
+ /**
+ * The icon shape.
+ */
+ shape: T;
+ /**
+ * The icon size.
+ *
+ * @default 'md'
+ */
+ size?: IconSize;
+};
+
+export type IconProps<T extends IconShape> = IconBaseProps<T> &
+ Pick<SVGPathsProps, 'orientation'> &
+ AdditionalProps<T>;
+
+type BuildClassNameConfig<T extends IconShape> = Pick<
+ IconProps<T>,
+ 'className'
+> &
+ Pick<AdditionalProps<T>, 'shape' | 'size'>;
+
+const buildClassName = <T extends IconShape>({
+ className,
+ shape,
+ size,
+}: BuildClassNameConfig<T>) => {
+ const classNames = ['icon', `icon--${shape}`, `icon--${size}`].map(
+ (key) => styles[key]
+ );
+
+ if (className) classNames.push(className);
+
+ return classNames.join(' ');
+};
+
+type ExtractedProps = 'className' | 'orientation' | 'shape' | 'size';
+
+export const Icon = <T extends IconShape>({
+ className = '',
+ orientation,
+ shape,
+ size = 'md',
+ ...props
+}: IconProps<T>) => {
+ const iconClass = buildClassName({ className, shape, size });
+
+ if (shape === 'hamburger')
+ return (
+ <HamburgerIcon
+ // Without casting Typescript complains because of props generic type
+ {...(props as Omit<IconProps<'hamburger'>, ExtractedProps>)}
+ className={iconClass}
+ />
+ );
+
+ if (shape === 'minus' || shape === 'plus')
+ return (
+ <PlusMinusIcon
+ // Without casting Typescript complains because of props generic type
+ {...(props as Omit<IconProps<'minus' | 'plus'>, ExtractedProps>)}
+ className={iconClass}
+ shape={shape}
+ />
+ );
+
+ const viewBox = shape === 'cc-by-sa' ? '0 0 100 40' : '0 0 100 100';
+
+ // Without casting Typescript complains because of props generic type
+ const { heading, description, ...remainingProps } = props as Omit<
+ IconProps<SVGIconShape>,
+ ExtractedProps
+ >;
+
+ return (
+ <svg
+ {...remainingProps}
+ className={iconClass}
+ viewBox={viewBox}
+ // eslint-disable-next-line react/jsx-no-literals
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ {heading ? <title>{heading}</title> : null}
+ {description ? <desc>{description}</desc> : null}
+ <SVGPaths orientation={orientation} shape={shape} />
+ </svg>
+ );
+};