@tui-cruises/mein-schiff-web-react-component-library 2.2.2 → 2.2.4
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 +25 -0
- package/icons/icons/circle.tsx +4 -4
- package/icons/icons/index.ts +2 -0
- package/icons/icons/mail.tsx +20 -0
- package/icons/icons/pause.tsx +20 -0
- package/icons/icons/phone.tsx +4 -4
- package/icons/icons/plus-circle.tsx +10 -4
- package/package.json +1 -1
- package/public/videos/placeholder.mp4 +0 -0
- package/src/components/core/AlertDialog/AlertDialog.tsx +11 -0
- package/src/components/core/Icon/Icon.tsx +79 -31
- package/src/components/core/IconButton/IconButton.tsx +91 -22
- package/src/components/core/Pictogram/Pictogram.tsx +70 -36
- package/src/components/core/Stepper/Stepper.tsx +1 -1
- package/src/helpers/responsive-property.ts +78 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
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.4](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/compare/v2.3.0...v2.2.4) (2025-07-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **icons:** add mail and pause icons, update circle, plus-circle and phone ([3c2291b](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/3c2291b0f829c88be2f9a5e13f400419c134c32a))
|
|
11
|
+
* **Tabs:** add `on` prop to control background context ([127dd83](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/127dd8319d205d6ec84c85c89e3b1959f3bfa9d7))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **EC-2101:** set IconButton as 'use client' to fix import problems ([9cd7b87](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/9cd7b8743309953e97a4768c9933a561ca3920e1))
|
|
17
|
+
* **EC-2101:** set IconButton as 'use client' to fix import problems ([6a1c421](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/6a1c42100b9f38fd3b6ed7c9b8d931afe4774b7c))
|
|
18
|
+
* **OM-2207:** form label with error style for data-invalid attribute ([285587c](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/285587cee026b4dc5c9d9ca5efa8fc87080244c8))
|
|
19
|
+
* remove FieldSetVariant from InputFieldInput.tsx ([75ba4f0](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/75ba4f03c80a43115e98c0d6c5cb0f913c824f1b))
|
|
20
|
+
* remove FieldSetVariant from InputFieldInput.tsx ([918af78](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/918af7869d319b3a00bd6aae4ee540907ef976d5))
|
|
21
|
+
* **Stepper:** update tracking parameter (EC-2069) ([ed64438](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/ed644389bfb5bd76ade919515598c015bb702a7d))
|
|
22
|
+
|
|
23
|
+
### [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)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* 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))
|
|
29
|
+
|
|
5
30
|
### [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)
|
|
6
31
|
|
|
7
32
|
|
package/icons/icons/circle.tsx
CHANGED
|
@@ -10,10 +10,10 @@ const SvgCircle = (props: SVGProps<SVGSVGElement>) => (
|
|
|
10
10
|
{...props}
|
|
11
11
|
>
|
|
12
12
|
<path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
fillRule="evenodd"
|
|
15
|
+
d="M1.25 12C1.25 6.063 6.063 1.25 12 1.25S22.75 6.063 22.75 12 17.937 22.75 12 22.75 1.25 17.937 1.25 12M12 2.75a9.25 9.25 0 1 0 0 18.5 9.25 9.25 0 0 0 0-18.5"
|
|
16
|
+
clipRule="evenodd"
|
|
17
17
|
/>
|
|
18
18
|
</svg>
|
|
19
19
|
);
|
package/icons/icons/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export const dynamicComponents = {
|
|
|
66
66
|
'log-in': dynamic(() => import('./log-in'), { ssr: false }),
|
|
67
67
|
'log-out': dynamic(() => import('./log-out'), { ssr: false }),
|
|
68
68
|
'mail-open': dynamic(() => import('./mail-open'), { ssr: false }),
|
|
69
|
+
mail: dynamic(() => import('./mail'), { ssr: false }),
|
|
69
70
|
map: dynamic(() => import('./map'), { ssr: false }),
|
|
70
71
|
'maps-arrow-diagonal': dynamic(() => import('./maps-arrow-diagonal'), {
|
|
71
72
|
ssr: false,
|
|
@@ -89,6 +90,7 @@ export const dynamicComponents = {
|
|
|
89
90
|
'nav-arrow-up': dynamic(() => import('./nav-arrow-up'), { ssr: false }),
|
|
90
91
|
'no-wifi': dynamic(() => import('./no-wifi'), { ssr: false }),
|
|
91
92
|
'old-trips': dynamic(() => import('./old-trips'), { ssr: false }),
|
|
93
|
+
pause: dynamic(() => import('./pause'), { ssr: false }),
|
|
92
94
|
percentage: dynamic(() => import('./percentage'), { ssr: false }),
|
|
93
95
|
phone: dynamic(() => import('./phone'), { ssr: false }),
|
|
94
96
|
pin: dynamic(() => import('./pin'), { ssr: false }),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SVGProps } from 'react';
|
|
2
|
+
const SvgMail = (props: SVGProps<SVGSVGElement>) => (
|
|
3
|
+
<svg
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="1em"
|
|
6
|
+
height="1em"
|
|
7
|
+
fill="none"
|
|
8
|
+
viewBox="0 0 24 24"
|
|
9
|
+
color="currentColor"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
fillRule="evenodd"
|
|
15
|
+
d="M2.75 7c0-.69.56-1.25 1.25-1.25h16c.69 0 1.25.56 1.25 1.25v10c0 .69-.56 1.25-1.25 1.25H4c-.69 0-1.25-.56-1.25-1.25zM4 4.25A2.75 2.75 0 0 0 1.25 7v10A2.75 2.75 0 0 0 4 19.75h16A2.75 2.75 0 0 0 22.75 17V7A2.75 2.75 0 0 0 20 4.25zm3.43 4.136a.75.75 0 1 0-.86 1.228l5 3.5a.75.75 0 0 0 .86 0l5-3.5a.75.75 0 1 0-.86-1.228L12 11.585z"
|
|
16
|
+
clipRule="evenodd"
|
|
17
|
+
/>
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
export default SvgMail;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SVGProps } from 'react';
|
|
2
|
+
const SvgPause = (props: SVGProps<SVGSVGElement>) => (
|
|
3
|
+
<svg
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="1em"
|
|
6
|
+
height="1em"
|
|
7
|
+
fill="none"
|
|
8
|
+
viewBox="0 0 24 24"
|
|
9
|
+
color="currentColor"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
fillRule="evenodd"
|
|
15
|
+
d="M9.25 4a.75.75 0 0 0-.75.75v14.5a.75.75 0 0 0 1.5 0V4.75A.75.75 0 0 0 9.25 4m5.5 0a.75.75 0 0 0-.75.75v14.5a.75.75 0 0 0 1.5 0V4.75a.75.75 0 0 0-.75-.75"
|
|
16
|
+
clipRule="evenodd"
|
|
17
|
+
/>
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
export default SvgPause;
|
package/icons/icons/phone.tsx
CHANGED
|
@@ -10,10 +10,10 @@ const SvgPhone = (props: SVGProps<SVGSVGElement>) => (
|
|
|
10
10
|
{...props}
|
|
11
11
|
>
|
|
12
12
|
<path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
fillRule="evenodd"
|
|
15
|
+
d="M2.028 4.73C1.798 3.214 3.016 2 4.458 2h3.521c.293 0 .556.182.66.456l1.366 3.633c.045.12.056.25.033.377l-.685 3.672c.841 1.98 2.231 3.31 4.537 4.502l3.628-.703a.7.7 0 0 1 .385.033l3.644 1.389a.7.7 0 0 1 .453.658v3.365c0 1.525-1.344 2.764-2.917 2.422l.15-.688-.15.688c-2.867-.624-8.177-2.21-11.897-5.929l.498-.498-.498.498C3.623 12.312 2.43 7.391 2.028 4.731l.696-.106zm2.43-1.322c-.675 0-1.125.537-1.038 1.112.388 2.566 1.521 7.119 4.762 10.36 3.425 3.424 8.404 4.94 11.2 5.548.614.133 1.21-.334 1.21-1.046v-2.88l-3.003-1.144-3.67.71a.7.7 0 0 1-.449-.062c-2.717-1.363-4.48-2.98-5.501-5.53a.7.7 0 0 1-.038-.39L8.617 6.4 7.492 3.408z"
|
|
16
|
+
clipRule="evenodd"
|
|
17
17
|
/>
|
|
18
18
|
</svg>
|
|
19
19
|
);
|
|
@@ -10,10 +10,16 @@ const SvgPlusCircle = (props: SVGProps<SVGSVGElement>) => (
|
|
|
10
10
|
{...props}
|
|
11
11
|
>
|
|
12
12
|
<path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
fillRule="evenodd"
|
|
15
|
+
d="M12 7.25a.75.75 0 0 1 .75.75v3.25H16a.75.75 0 0 1 0 1.5h-3.25V16a.75.75 0 0 1-1.5 0v-3.25H8a.75.75 0 0 1 0-1.5h3.25V8a.75.75 0 0 1 .75-.75"
|
|
16
|
+
clipRule="evenodd"
|
|
17
|
+
/>
|
|
18
|
+
<path
|
|
19
|
+
fill="currentColor"
|
|
20
|
+
fillRule="evenodd"
|
|
21
|
+
d="M12 2.75a9.25 9.25 0 1 0 0 18.5 9.25 9.25 0 0 0 0-18.5M1.25 12C1.25 6.063 6.063 1.25 12 1.25S22.75 6.063 22.75 12 17.937 22.75 12 22.75 1.25 17.937 1.25 12"
|
|
22
|
+
clipRule="evenodd"
|
|
17
23
|
/>
|
|
18
24
|
</svg>
|
|
19
25
|
);
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -47,6 +47,11 @@ type Props = PropsWithChildren<{
|
|
|
47
47
|
*/
|
|
48
48
|
open?: boolean;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* A React node that will be rendered as the trigger for the modal.
|
|
52
|
+
*/
|
|
53
|
+
trigger?: ReactNode;
|
|
54
|
+
|
|
50
55
|
/**
|
|
51
56
|
* Callback fired when the modal's open state changes.
|
|
52
57
|
*/
|
|
@@ -254,9 +259,15 @@ export const AlertDialog: FC<Props> = ({
|
|
|
254
259
|
container,
|
|
255
260
|
centerItems = true,
|
|
256
261
|
containerClassName,
|
|
262
|
+
trigger,
|
|
257
263
|
}) => {
|
|
258
264
|
return (
|
|
259
265
|
<AlertDialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
|
266
|
+
{trigger && (
|
|
267
|
+
<AlertDialogPrimitive.Trigger asChild>
|
|
268
|
+
{trigger}
|
|
269
|
+
</AlertDialogPrimitive.Trigger>
|
|
270
|
+
)}
|
|
260
271
|
<AlertDialogPrimitive.Portal container={container || undefined}>
|
|
261
272
|
<AlertDialogPrimitive.Overlay
|
|
262
273
|
className={twJoin(
|
|
@@ -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,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';
|
|
@@ -87,7 +87,7 @@ const Stepper = (props: StepperProps) => {
|
|
|
87
87
|
variant="secondary"
|
|
88
88
|
on={on}
|
|
89
89
|
data-track-click-element={EventClickElement.Button}
|
|
90
|
-
data-track-click-label={`${trackingName}stepper-decrease`}
|
|
90
|
+
data-track-click-label={`${trackingName}-stepper-decrease`}
|
|
91
91
|
aria-label={i18n?.stepperDecrease ?? 'Anzahl verringern'}
|
|
92
92
|
/>
|
|
93
93
|
<div
|
|
@@ -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
|
+
};
|