1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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>
);
};
|