@tui-cruises/mein-schiff-web-react-component-library 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/src/components/core/Icon/Icon.tsx +79 -31
- package/src/components/core/IconButton/IconButton.tsx +91 -22
- package/src/components/core/InputField/InputFieldInput.tsx +6 -4
- package/src/components/core/Pictogram/Pictogram.tsx +70 -36
- package/src/helpers/responsive-property.ts +78 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [2.2.3](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/compare/v2.2.2...v2.2.3) (2025-07-21)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add responsive sizes to Icons, Pictograms and IconButtons. Removed numeric size properties ([49704b69](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/75ba4f03c80a43115e98c0d6c5cb0f913c824f1b))
|
|
11
|
+
|
|
12
|
+
### [2.2.2](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/compare/v2.2.1...v2.2.2) (2025-07-18)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* set IconButton as 'use client' to fix import problems ([9cd7b87](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/9cd7b8743309953e97a4768c9933a561ca3920e1))
|
|
18
|
+
* remove FieldSetVariant from InputFieldInput.tsx ([918af78](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/918af7869d319b3a00bd6aae4ee540907ef976d5))
|
|
19
|
+
|
|
5
20
|
### [2.2.1](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/compare/v2.2.0...v2.2.1) (2025-07-18)
|
|
6
21
|
|
|
7
22
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { dynamicComponents as dynamicComponentsIcons } from '../../../../icons/icons';
|
|
4
4
|
import type { ComponentType, SVGProps } from 'react';
|
|
5
|
-
import { twJoin } from 'tailwind-merge';
|
|
5
|
+
import { twJoin, twMerge } from 'tailwind-merge';
|
|
6
|
+
import {
|
|
7
|
+
ResponsiveValueClassMap,
|
|
8
|
+
getClassNamesFromMap,
|
|
9
|
+
ResponsiveValue,
|
|
10
|
+
} from '../../../helpers/responsive-property';
|
|
6
11
|
|
|
7
12
|
// Type for local icons
|
|
8
13
|
type DynamicComponentIconName = keyof typeof dynamicComponentsIcons;
|
|
@@ -10,6 +15,15 @@ type DynamicComponentIconName = keyof typeof dynamicComponentsIcons;
|
|
|
10
15
|
// Union of both types to create a comprehensive IconName type
|
|
11
16
|
export type IconName = DynamicComponentIconName;
|
|
12
17
|
|
|
18
|
+
/**
|
|
19
|
+
* The size of the icon.
|
|
20
|
+
* 'xs' (16x16), 'sm' (20x20),
|
|
21
|
+
* 'md' (24x24), 'lg' (32x32).
|
|
22
|
+
*
|
|
23
|
+
* @default 'md'
|
|
24
|
+
*/
|
|
25
|
+
type IconSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
26
|
+
|
|
13
27
|
export type IconProps = {
|
|
14
28
|
/**
|
|
15
29
|
* Additional TailwindCSS utilities can be added with this prop.
|
|
@@ -27,56 +41,90 @@ export type IconProps = {
|
|
|
27
41
|
*
|
|
28
42
|
* @default 'md'
|
|
29
43
|
*/
|
|
30
|
-
size?:
|
|
44
|
+
size?: ResponsiveValue<IconSize>;
|
|
31
45
|
};
|
|
32
46
|
|
|
33
47
|
/**
|
|
34
|
-
* Component to display an icon, based on the [
|
|
48
|
+
* Component to display an icon, based on the [Iconoir icon library](https://iconoir.com/)
|
|
49
|
+
* or a custom local icon library.
|
|
35
50
|
*
|
|
36
51
|
* @component
|
|
37
52
|
* @param {IconProps} props - The props for the Icon component.
|
|
38
53
|
* @returns {JSX.Element | null} The rendered icon component or null if the icon is not found.
|
|
39
54
|
*/
|
|
40
55
|
const Icon = (props: IconProps) => {
|
|
41
|
-
const {
|
|
56
|
+
const { name, size } = props;
|
|
42
57
|
|
|
43
58
|
const IconComponent = getIconComponent(name);
|
|
44
59
|
if (!IconComponent) {
|
|
45
60
|
return null;
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
|
|
63
|
+
let classNameContainsSize = ['size-', 'h-', 'w-'].some(prefix =>
|
|
64
|
+
props.className?.includes(prefix),
|
|
65
|
+
);
|
|
49
66
|
|
|
50
67
|
return (
|
|
51
|
-
<
|
|
52
|
-
className={
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
<span
|
|
69
|
+
className={twMerge(
|
|
70
|
+
'block',
|
|
71
|
+
!classNameContainsSize && getClassNamesFromMap(sizeCssMap, size, 'md'),
|
|
72
|
+
props.className,
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<IconComponent
|
|
76
|
+
className={twJoin(
|
|
77
|
+
'pointer-events-none block size-full',
|
|
78
|
+
name === 'loading' && 'animate-spin',
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
81
|
+
</span>
|
|
56
82
|
);
|
|
57
83
|
};
|
|
58
84
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
const sizeCssMap: ResponsiveValueClassMap<IconSize> = {
|
|
86
|
+
initial: {
|
|
87
|
+
xs: 'size-[16px]',
|
|
88
|
+
sm: 'size-[20px]',
|
|
89
|
+
md: 'size-[24px]',
|
|
90
|
+
lg: 'size-[32px]',
|
|
91
|
+
},
|
|
92
|
+
xs: {
|
|
93
|
+
xs: 'xs:size-[16px]',
|
|
94
|
+
sm: 'xs:size-[20px]',
|
|
95
|
+
md: 'xs:size-[24px]',
|
|
96
|
+
lg: 'xs:size-[32px]',
|
|
97
|
+
},
|
|
98
|
+
sm: {
|
|
99
|
+
xs: 'sm:size-[16px]',
|
|
100
|
+
sm: 'sm:size-[20px]',
|
|
101
|
+
md: 'sm:size-[24px]',
|
|
102
|
+
lg: 'sm:size-[32px]',
|
|
103
|
+
},
|
|
104
|
+
md: {
|
|
105
|
+
xs: 'md:size-[16px]',
|
|
106
|
+
sm: 'md:size-[20px]',
|
|
107
|
+
md: 'md:size-[24px]',
|
|
108
|
+
lg: 'md:size-[32px]',
|
|
109
|
+
},
|
|
110
|
+
lg: {
|
|
111
|
+
xs: 'lg:size-[16px]',
|
|
112
|
+
sm: 'lg:size-[20px]',
|
|
113
|
+
md: 'lg:size-[24px]',
|
|
114
|
+
lg: 'lg:size-[32px]',
|
|
115
|
+
},
|
|
116
|
+
xl: {
|
|
117
|
+
xs: 'xl:size-[16px]',
|
|
118
|
+
sm: 'xl:size-[20px]',
|
|
119
|
+
md: 'xl:size-[24px]',
|
|
120
|
+
lg: 'xl:size-[32px]',
|
|
121
|
+
},
|
|
122
|
+
xxl: {
|
|
123
|
+
xs: '2xl:size-[16px]',
|
|
124
|
+
sm: '2xl:size-[20px]',
|
|
125
|
+
md: '2xl:size-[24px]',
|
|
126
|
+
lg: '2xl:size-[32px]',
|
|
127
|
+
},
|
|
80
128
|
};
|
|
81
129
|
|
|
82
130
|
/**
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
import { twJoin, twMerge } from 'tailwind-merge';
|
|
10
10
|
import { Slot, Slottable } from '@radix-ui/react-slot';
|
|
11
11
|
import { wrapSlottableChildren } from '../../../helpers/slot';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getClassNamesFromMap,
|
|
14
|
+
ResponsiveValue,
|
|
15
|
+
ResponsiveValueClassMap,
|
|
16
|
+
} from '../../../helpers/responsive-property';
|
|
13
17
|
|
|
14
18
|
type CommonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
15
19
|
asChild?: boolean;
|
|
@@ -18,14 +22,18 @@ type CommonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
|
18
22
|
on?: 'white' | 'gray' | 'ocean' | 'image';
|
|
19
23
|
};
|
|
20
24
|
|
|
25
|
+
type RegularSize = 'sm' | 'md' | 'lg';
|
|
26
|
+
type GhostButtonSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
27
|
+
|
|
21
28
|
export type IconButtonPrimaryAndSecondaryProps = CommonProps & {
|
|
22
29
|
variant?: 'primary' | 'secondary';
|
|
23
|
-
|
|
30
|
+
/** The button's size. Defaults to `sm`. */
|
|
31
|
+
size?: ResponsiveValue<RegularSize>;
|
|
24
32
|
};
|
|
25
33
|
|
|
26
34
|
export type IconButtonGhostProps = CommonProps & {
|
|
27
35
|
variant: 'ghost';
|
|
28
|
-
size?:
|
|
36
|
+
size?: ResponsiveValue<GhostButtonSize>;
|
|
29
37
|
};
|
|
30
38
|
|
|
31
39
|
export type IconButtonProps =
|
|
@@ -37,7 +45,7 @@ const IconButtonRenderFunction: ForwardRefRenderFunction<
|
|
|
37
45
|
IconButtonProps
|
|
38
46
|
> = (props, ref) => {
|
|
39
47
|
const {
|
|
40
|
-
size
|
|
48
|
+
size,
|
|
41
49
|
variant = 'primary',
|
|
42
50
|
on = 'white',
|
|
43
51
|
iconName,
|
|
@@ -47,20 +55,10 @@ const IconButtonRenderFunction: ForwardRefRenderFunction<
|
|
|
47
55
|
...rest
|
|
48
56
|
} = props;
|
|
49
57
|
|
|
50
|
-
// Assuming `useScreenNameAtLeast` is returning a screen size string
|
|
51
|
-
const isMdOrAbove = useScreenNameAtLeast('md', 'md');
|
|
52
|
-
|
|
53
|
-
let resolvedIconSize = size;
|
|
54
|
-
if (size === 'xs' && isMdOrAbove) {
|
|
55
|
-
resolvedIconSize = 'md';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
58
|
const buttonClassNames = twMerge(
|
|
59
59
|
'inline-flex rounded-full transition-all',
|
|
60
60
|
|
|
61
|
-
size
|
|
62
|
-
size === 'sm' && 'p-3 md:p-2', // 36px
|
|
63
|
-
size === 'md' && 'p-3.5 md:p-3', // 52px md:48px
|
|
61
|
+
getClassNamesFromMap(iconCssMap, size, 'md'),
|
|
64
62
|
|
|
65
63
|
// Variants
|
|
66
64
|
variant === 'primary' && [
|
|
@@ -130,20 +128,17 @@ const IconButtonRenderFunction: ForwardRefRenderFunction<
|
|
|
130
128
|
'focus:outline-none',
|
|
131
129
|
'disabled:!pointer-events-none disabled:text-secondary-marine-48',
|
|
132
130
|
|
|
133
|
-
size
|
|
131
|
+
getClassNamesFromMap(buttonCssMap, size, 'md'),
|
|
134
132
|
variant !== 'ghost' && ['gap-2'],
|
|
135
|
-
variant === 'ghost' &&
|
|
136
|
-
size
|
|
137
|
-
size === 'sm' && 'gap-1',
|
|
138
|
-
size === 'md' && 'gap-1',
|
|
139
|
-
],
|
|
133
|
+
variant === 'ghost' &&
|
|
134
|
+
getClassNamesFromMap(ghostButtonCssMap, size, 'md'),
|
|
140
135
|
iconPosition === 'right' && 'flex-row-reverse',
|
|
141
136
|
|
|
142
137
|
rest.className,
|
|
143
138
|
)}
|
|
144
139
|
>
|
|
145
140
|
<div className={buttonClassNames}>
|
|
146
|
-
<Icon name={iconName} size={
|
|
141
|
+
<Icon name={iconName} size={size} />
|
|
147
142
|
</div>
|
|
148
143
|
|
|
149
144
|
<Slottable>
|
|
@@ -159,6 +154,80 @@ const IconButtonRenderFunction: ForwardRefRenderFunction<
|
|
|
159
154
|
);
|
|
160
155
|
};
|
|
161
156
|
|
|
157
|
+
/**
|
|
158
|
+
* The css map for the responsive value css used in icon.
|
|
159
|
+
*/
|
|
160
|
+
const iconCssMap: ResponsiveValueClassMap<GhostButtonSize> = {
|
|
161
|
+
initial: {
|
|
162
|
+
xs: 'p-0.5',
|
|
163
|
+
sm: 'p-2',
|
|
164
|
+
md: 'p-3',
|
|
165
|
+
lg: undefined,
|
|
166
|
+
},
|
|
167
|
+
xs: {
|
|
168
|
+
xs: 'xs:p-0.5',
|
|
169
|
+
sm: 'xs:p-2',
|
|
170
|
+
md: 'xs:p-3',
|
|
171
|
+
lg: undefined,
|
|
172
|
+
},
|
|
173
|
+
sm: {
|
|
174
|
+
xs: 'sm:p-0.5',
|
|
175
|
+
sm: 'sm:p-2',
|
|
176
|
+
md: 'sm:p-3',
|
|
177
|
+
lg: undefined,
|
|
178
|
+
},
|
|
179
|
+
md: {
|
|
180
|
+
xs: 'md:p-0.5',
|
|
181
|
+
sm: 'md:p-2',
|
|
182
|
+
md: 'md:p-3',
|
|
183
|
+
lg: undefined,
|
|
184
|
+
},
|
|
185
|
+
lg: {
|
|
186
|
+
xs: 'lg:p-0.5',
|
|
187
|
+
sm: 'lg:p-2',
|
|
188
|
+
md: 'lg:p-3',
|
|
189
|
+
lg: undefined,
|
|
190
|
+
},
|
|
191
|
+
xl: {
|
|
192
|
+
xs: 'xl:p-0.5',
|
|
193
|
+
sm: 'xl:p-2',
|
|
194
|
+
md: 'xl:p-3',
|
|
195
|
+
lg: undefined,
|
|
196
|
+
},
|
|
197
|
+
xxl: {
|
|
198
|
+
xs: '2xl:p-0.5',
|
|
199
|
+
sm: '2xl:p-2',
|
|
200
|
+
md: '2xl:p-3',
|
|
201
|
+
lg: undefined,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The css map for the responsive value css used in button.
|
|
207
|
+
*/
|
|
208
|
+
const buttonCssMap: ResponsiveValueClassMap<GhostButtonSize> = {
|
|
209
|
+
initial: size => (size === 'md' ? 'text-base' : 'text-sm'),
|
|
210
|
+
xs: size => (size === 'md' ? 'xs:text-base' : 'xs:text-sm'),
|
|
211
|
+
sm: size => (size === 'md' ? 'sm:text-base' : 'sm:text-sm'),
|
|
212
|
+
md: size => (size === 'md' ? 'md:text-base' : 'md:text-sm'),
|
|
213
|
+
lg: size => (size === 'md' ? 'lg:text-base' : 'lg:text-sm'),
|
|
214
|
+
xl: size => (size === 'md' ? 'xl:text-base' : 'xl:text-sm'),
|
|
215
|
+
xxl: size => (size === 'md' ? '2xl:text-base' : '2xl:text-sm'),
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The css map for the responsive value css used in the ghost button variant.
|
|
220
|
+
*/
|
|
221
|
+
const ghostButtonCssMap: ResponsiveValueClassMap<GhostButtonSize> = {
|
|
222
|
+
initial: { xs: 'gap-0.5', sm: 'gap-1', md: 'gap-1', lg: undefined },
|
|
223
|
+
xs: { xs: 'xs:gap-0.5', sm: 'xs:gap-1', md: 'xs:gap-1', lg: undefined },
|
|
224
|
+
sm: { xs: 'sm:gap-0.5', sm: 'sm:gap-1', md: 'sm:gap-1', lg: undefined },
|
|
225
|
+
md: { xs: 'md:gap-0.5', sm: 'md:gap-1', md: 'md:gap-1', lg: undefined },
|
|
226
|
+
lg: { xs: 'lg:gap-0.5', sm: 'lg:gap-1', md: 'lg:gap-1', lg: undefined },
|
|
227
|
+
xl: { xs: 'xl:gap-0.5', sm: 'xl:gap-1', md: 'xl:gap-1', lg: undefined },
|
|
228
|
+
xxl: { xs: '2xl:gap-0.5', sm: '2xl:gap-1', md: '2xl:gap-1', lg: undefined },
|
|
229
|
+
};
|
|
230
|
+
|
|
162
231
|
/**
|
|
163
232
|
* IconButton is a simple round button component, displaying one icon with an optional label.
|
|
164
233
|
*
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
forwardRef,
|
|
3
3
|
ForwardRefRenderFunction,
|
|
4
|
-
HTMLAttributes,
|
|
4
|
+
// HTMLAttributes,
|
|
5
5
|
InputHTMLAttributes,
|
|
6
6
|
useEffect,
|
|
7
7
|
useImperativeHandle,
|
|
8
8
|
useRef,
|
|
9
9
|
} from 'react';
|
|
10
|
+
|
|
10
11
|
import { twJoin } from 'tailwind-merge';
|
|
11
|
-
import { FieldsetProps } from '../Fieldset';
|
|
12
|
+
// import { FieldsetProps } from '../Fieldset';
|
|
12
13
|
|
|
13
14
|
type ElementAttributes = Omit<
|
|
14
15
|
InputHTMLAttributes<HTMLInputElement>,
|
|
@@ -16,14 +17,15 @@ type ElementAttributes = Omit<
|
|
|
16
17
|
>;
|
|
17
18
|
|
|
18
19
|
export type InputFieldInputProps = ElementAttributes & {
|
|
19
|
-
fieldsetVariant?: FieldsetProps['variant'];
|
|
20
|
+
// fieldsetVariant?: FieldsetProps['variant'];
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
const InputFieldInputRenderFunction: ForwardRefRenderFunction<
|
|
23
24
|
HTMLInputElement,
|
|
24
25
|
InputFieldInputProps
|
|
25
26
|
> = (props, forwardedRef) => {
|
|
26
|
-
let { fieldsetVariant, ...attrs } = props;
|
|
27
|
+
// let { fieldsetVariant, ...attrs } = props;
|
|
28
|
+
let { ...attrs } = props;
|
|
27
29
|
let ref = useRef<HTMLInputElement>(null);
|
|
28
30
|
|
|
29
31
|
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { FC } from 'react';
|
|
2
2
|
import { dynamicComponents as dynamicComponentsPictograms } from '../../../../icons/pictograms';
|
|
3
3
|
import { twMerge } from 'tailwind-merge';
|
|
4
|
+
import {
|
|
5
|
+
getClassNamesFromMap,
|
|
6
|
+
ResponsiveValue,
|
|
7
|
+
ResponsiveValueClassMap,
|
|
8
|
+
} from '../../../helpers/responsive-property';
|
|
4
9
|
|
|
5
10
|
type CollectionPictograms = typeof dynamicComponentsPictograms;
|
|
6
11
|
|
|
@@ -8,58 +13,87 @@ export type PictogramName = keyof CollectionPictograms;
|
|
|
8
13
|
|
|
9
14
|
type IconComponent = CollectionPictograms[PictogramName];
|
|
10
15
|
|
|
11
|
-
export type PictogramProps = {
|
|
12
|
-
className?: string;
|
|
13
|
-
name: PictogramName;
|
|
14
|
-
size?: number | 'sm' | 'md';
|
|
15
|
-
};
|
|
16
|
-
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
17
|
+
* The size of the icon.
|
|
18
|
+
* 'sm' (40x40),
|
|
19
|
+
* 'md' (60x60)
|
|
20
|
+
*
|
|
21
|
+
* @default 'sm'
|
|
19
22
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
default:
|
|
27
|
-
return {
|
|
28
|
-
width: size?.toString() || '40',
|
|
29
|
-
height: size?.toString() || '40',
|
|
30
|
-
};
|
|
31
|
-
}
|
|
23
|
+
type PictogramSize = 'sm' | 'md';
|
|
24
|
+
|
|
25
|
+
export type PictogramProps = {
|
|
26
|
+
className?: string;
|
|
27
|
+
name: PictogramName;
|
|
28
|
+
size?: ResponsiveValue<PictogramSize>;
|
|
32
29
|
};
|
|
33
30
|
|
|
34
31
|
/**
|
|
35
32
|
* Renders the icon.
|
|
36
33
|
*/
|
|
37
34
|
const renderIcon = (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
Element: IconComponent,
|
|
36
|
+
size: PictogramProps['size'],
|
|
37
|
+
wrapperAttrs: Record<string, string>,
|
|
41
38
|
) => {
|
|
42
|
-
|
|
39
|
+
let classNameContainsSize = ['size-', 'h-', 'w-'].some(prefix =>
|
|
40
|
+
wrapperAttrs.className?.includes(prefix),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const className = twMerge(
|
|
44
|
+
'inline-block',
|
|
45
|
+
!classNameContainsSize && getClassNamesFromMap(sizeCssMap, size, 'sm'),
|
|
46
|
+
wrapperAttrs.className,
|
|
47
|
+
);
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
return (
|
|
50
|
+
<div {...wrapperAttrs} className={className}>
|
|
51
|
+
<Element className="block size-full" viewBox="0 0 40 40" />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
49
54
|
};
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
|
-
*
|
|
57
|
+
* Renders a pictogram with the specified size and icon.
|
|
53
58
|
*/
|
|
54
|
-
const Pictogram: FC<PictogramProps> = ({ size
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const Pictogram: FC<PictogramProps> = ({ size, name, ...rest }) => {
|
|
60
|
+
const Element = dynamicComponentsPictograms[name];
|
|
61
|
+
if (!Element) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
return renderIcon(Element, size, rest);
|
|
66
|
+
};
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
const sizeCssMap: ResponsiveValueClassMap<PictogramSize> = {
|
|
69
|
+
initial: {
|
|
70
|
+
sm: 'size-[40px]',
|
|
71
|
+
md: 'size-[60px]',
|
|
72
|
+
},
|
|
73
|
+
xs: {
|
|
74
|
+
sm: 'xs:size-[40px]',
|
|
75
|
+
md: 'xs:size-[60px]',
|
|
76
|
+
},
|
|
77
|
+
sm: {
|
|
78
|
+
sm: 'sm:size-[40px]',
|
|
79
|
+
md: 'sm:size-[60px]',
|
|
80
|
+
},
|
|
81
|
+
md: {
|
|
82
|
+
sm: 'md:size-[40px]',
|
|
83
|
+
md: 'md:size-[60px]',
|
|
84
|
+
},
|
|
85
|
+
lg: {
|
|
86
|
+
sm: 'lg:size-[40px]',
|
|
87
|
+
md: 'lg:size-[60px]',
|
|
88
|
+
},
|
|
89
|
+
xl: {
|
|
90
|
+
sm: 'xl:size-[40px]',
|
|
91
|
+
md: 'xl:size-[60px]',
|
|
92
|
+
},
|
|
93
|
+
xxl: {
|
|
94
|
+
sm: '2xl:size-[40px]',
|
|
95
|
+
md: '2xl:size-[60px]',
|
|
96
|
+
},
|
|
63
97
|
};
|
|
64
98
|
|
|
65
99
|
Pictogram.displayName = 'Pictogram';
|
|
@@ -4,21 +4,42 @@ import { ScreenName } from './screen';
|
|
|
4
4
|
|
|
5
5
|
export type ResponsiveValue<T> = T | [T, ScreenNameIndexedValues<T>?];
|
|
6
6
|
|
|
7
|
+
type ScreenNameWithInitial = ScreenName | 'initial';
|
|
8
|
+
|
|
7
9
|
type ScreenNameIndexedValues<T> = {
|
|
8
10
|
[screenName in ScreenName]?: T;
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
type OptionalScreenNameIndexedValuesWithInitial<T> = {
|
|
14
|
+
[screenName in ScreenNameWithInitial]: T | undefined;
|
|
15
|
+
};
|
|
16
|
+
|
|
11
17
|
type ScreenNameIndexedValuesWithInitial<T> = {
|
|
12
|
-
[screenName in
|
|
18
|
+
[screenName in ScreenNameWithInitial]: T;
|
|
13
19
|
};
|
|
14
20
|
|
|
21
|
+
// export type ResponsiveValueClassMap<T extends string | number> = {
|
|
22
|
+
// [key in ScreenNameWithInitial]?: Record<T, string | undefined> | ((value: T) => string | undefined)
|
|
23
|
+
// }
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Responsive value class map.
|
|
27
|
+
* Provides a mapping for each screen name to a responsive value or a function that resolves the value.
|
|
28
|
+
* You can use this to create responsive styles based on the screen size when you want to avoid any hook based solutions.
|
|
29
|
+
* This is a more complex approach than using a simple responsive value, but it enables server side rendering and avoids any layout shifts based on initial screen name resolution.
|
|
30
|
+
*/
|
|
31
|
+
export type ResponsiveValueClassMap<T extends string | number> = Record<
|
|
32
|
+
ScreenNameWithInitial,
|
|
33
|
+
Record<T, string | undefined> | ((value: T) => string | undefined)
|
|
34
|
+
>;
|
|
35
|
+
|
|
15
36
|
/**
|
|
16
37
|
* Resolves given responsive property value to screen name indexed values including an initial key for the initial screen.
|
|
17
38
|
*/
|
|
18
39
|
export const getScreenIndexedResponsiveValue = <T extends any = any>(
|
|
19
40
|
propValue: ResponsiveValue<T> | undefined,
|
|
20
41
|
defaultValue: T,
|
|
21
|
-
):
|
|
42
|
+
): OptionalScreenNameIndexedValuesWithInitial<T> => {
|
|
22
43
|
if (typeof propValue === 'undefined' || !Array.isArray(propValue)) {
|
|
23
44
|
return {
|
|
24
45
|
initial: propValue ?? defaultValue,
|
|
@@ -64,29 +85,33 @@ export const getScreenIndexedResponsiveValueForEachScreen = <
|
|
|
64
85
|
>(
|
|
65
86
|
propValue: ResponsiveValue<T> | undefined,
|
|
66
87
|
defaultValue: T,
|
|
67
|
-
):
|
|
88
|
+
): ScreenNameIndexedValuesWithInitial<T> => {
|
|
68
89
|
const screenNameIndexedValues = getScreenIndexedResponsiveValue(
|
|
69
90
|
propValue,
|
|
70
91
|
defaultValue,
|
|
71
92
|
);
|
|
72
93
|
|
|
73
|
-
const screens: Record<
|
|
74
|
-
|
|
75
|
-
sm: screenNameIndexedValues.sm,
|
|
76
|
-
md: screenNameIndexedValues.md,
|
|
77
|
-
lg: screenNameIndexedValues.lg,
|
|
78
|
-
xl: screenNameIndexedValues.xl,
|
|
79
|
-
xxl: screenNameIndexedValues.xxl,
|
|
94
|
+
const screens: Record<ScreenNameWithInitial, T | undefined> = {
|
|
95
|
+
...screenNameIndexedValues,
|
|
80
96
|
};
|
|
81
97
|
|
|
82
|
-
screens.
|
|
98
|
+
screens.initial = screens.initial ?? defaultValue;
|
|
99
|
+
screens.xs = screens.xs ?? screens.initial;
|
|
83
100
|
screens.sm = screens.sm ?? screens.xs;
|
|
84
101
|
screens.md = screens.md ?? screens.sm;
|
|
85
102
|
screens.lg = screens.lg ?? screens.md;
|
|
86
103
|
screens.xl = screens.xl ?? screens.lg;
|
|
87
104
|
screens.xxl = screens.xxl ?? screens.xl;
|
|
88
105
|
|
|
89
|
-
return
|
|
106
|
+
return {
|
|
107
|
+
initial: screens.initial ?? defaultValue,
|
|
108
|
+
xs: screens.xs ?? screens.initial,
|
|
109
|
+
sm: screens.sm ?? screens.xs,
|
|
110
|
+
md: screens.md ?? screens.sm,
|
|
111
|
+
lg: screens.lg ?? screens.md,
|
|
112
|
+
xl: screens.xl ?? screens.lg,
|
|
113
|
+
xxl: screens.xxl ?? screens.xl,
|
|
114
|
+
};
|
|
90
115
|
};
|
|
91
116
|
|
|
92
117
|
/**
|
|
@@ -116,3 +141,44 @@ export const getResponsiveValue = <T extends any = any>(
|
|
|
116
141
|
|
|
117
142
|
return propValue[0];
|
|
118
143
|
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generates css for each responsive value based on the rules from the given screen/value map.
|
|
147
|
+
* See the `Icon` component for a simple example of how to use this or the `IconButton` component for a more complex example.
|
|
148
|
+
*/
|
|
149
|
+
export const getClassNamesFromMap = <T extends string | number = string>(
|
|
150
|
+
map: ResponsiveValueClassMap<T>,
|
|
151
|
+
value: ResponsiveValue<T> | undefined,
|
|
152
|
+
defaultValue: T,
|
|
153
|
+
): string => {
|
|
154
|
+
let screenIndexed = getScreenIndexedResponsiveValue<T>(value, defaultValue);
|
|
155
|
+
|
|
156
|
+
let classList = Object.entries(screenIndexed).reduce<string[]>(
|
|
157
|
+
(list, [screenArg, value]) => {
|
|
158
|
+
if (typeof value === 'undefined') {
|
|
159
|
+
return list;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let screen = screenArg as keyof typeof screenIndexed;
|
|
163
|
+
let resolvedScreenIndexedValues = map[screen];
|
|
164
|
+
|
|
165
|
+
if (typeof resolvedScreenIndexedValues === 'undefined') {
|
|
166
|
+
return list;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let screenValue =
|
|
170
|
+
typeof resolvedScreenIndexedValues === 'function'
|
|
171
|
+
? resolvedScreenIndexedValues(value)
|
|
172
|
+
: resolvedScreenIndexedValues?.[value];
|
|
173
|
+
|
|
174
|
+
if (typeof screenValue === 'undefined') {
|
|
175
|
+
return list;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return [...list, screenValue];
|
|
179
|
+
},
|
|
180
|
+
[],
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return classList.join(' ');
|
|
184
|
+
};
|