@shipfox/react-ui 0.8.0 → 0.10.0
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/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-check.log +3 -3
- package/.turbo/turbo-type.log +2 -2
- package/CHANGELOG.md +12 -0
- package/dist/components/alert/alert.d.ts +15 -5
- package/dist/components/alert/alert.d.ts.map +1 -1
- package/dist/components/alert/alert.js +122 -22
- package/dist/components/alert/alert.js.map +1 -1
- package/dist/components/alert/alert.stories.js +121 -6
- package/dist/components/alert/alert.stories.js.map +1 -1
- package/dist/components/button/button-link.js +1 -1
- package/dist/components/button/button-link.js.map +1 -1
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +4 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/icon-button.d.ts.map +1 -1
- package/dist/components/button/icon-button.js +4 -1
- package/dist/components/button/icon-button.js.map +1 -1
- package/dist/components/checkbox/checkbox-links.d.ts.map +1 -1
- package/dist/components/checkbox/checkbox-links.js +8 -2
- package/dist/components/checkbox/checkbox-links.js.map +1 -1
- package/dist/components/checkbox/checkbox.stories.js +4 -4
- package/dist/components/checkbox/checkbox.stories.js.map +1 -1
- package/dist/components/icon/icon.d.ts +3 -17
- package/dist/components/icon/icon.d.ts.map +1 -1
- package/dist/components/icon/icon.js +21 -13
- package/dist/components/icon/icon.js.map +1 -1
- package/dist/components/icon/remixicon-registry.d.ts +5 -0
- package/dist/components/icon/remixicon-registry.d.ts.map +1 -0
- package/dist/components/icon/remixicon-registry.js +14 -0
- package/dist/components/icon/remixicon-registry.js.map +1 -0
- package/package.json +1 -1
- package/src/components/alert/alert.stories.tsx +103 -2
- package/src/components/alert/alert.tsx +163 -16
- package/src/components/button/button-link.tsx +1 -1
- package/src/components/button/button.tsx +2 -1
- package/src/components/button/icon-button.tsx +2 -1
- package/src/components/checkbox/checkbox-links.tsx +5 -3
- package/src/components/checkbox/checkbox.stories.tsx +20 -4
- package/src/components/icon/icon.tsx +23 -13
- package/src/components/icon/remixicon-registry.ts +24 -0
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import {cva, type VariantProps} from 'class-variance-authority';
|
|
2
2
|
import {Icon} from 'components/icon';
|
|
3
|
-
import type
|
|
3
|
+
import {AnimatePresence, motion, type Transition} from 'framer-motion';
|
|
4
|
+
import {
|
|
5
|
+
type ComponentProps,
|
|
6
|
+
createContext,
|
|
7
|
+
type MouseEvent,
|
|
8
|
+
useCallback,
|
|
9
|
+
useContext,
|
|
10
|
+
useEffect,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
4
14
|
import {cn} from 'utils/cn';
|
|
5
15
|
|
|
6
16
|
const alertVariants = cva(
|
|
@@ -51,21 +61,137 @@ const closeIconVariants = cva('w-16 h-16', {
|
|
|
51
61
|
},
|
|
52
62
|
});
|
|
53
63
|
|
|
54
|
-
|
|
64
|
+
const alertDefaultTransition: Transition = {
|
|
65
|
+
duration: 0.2,
|
|
66
|
+
ease: 'easeInOut',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type AlertContextValue = {
|
|
70
|
+
isOpen: boolean;
|
|
71
|
+
onClose: () => void;
|
|
72
|
+
variant: VariantProps<typeof alertVariants>['variant'];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const AlertContext = createContext<AlertContextValue | null>(null);
|
|
76
|
+
|
|
77
|
+
function useAlertContext() {
|
|
78
|
+
const context = useContext(AlertContext);
|
|
79
|
+
if (!context) {
|
|
80
|
+
throw new Error('Alert components must be used within an Alert component');
|
|
81
|
+
}
|
|
82
|
+
return context;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type AlertProps = ComponentProps<'div'> &
|
|
86
|
+
VariantProps<typeof alertVariants> & {
|
|
87
|
+
open?: boolean;
|
|
88
|
+
defaultOpen?: boolean;
|
|
89
|
+
onOpenChange?: (open: boolean) => void;
|
|
90
|
+
animated?: boolean;
|
|
91
|
+
transition?: Transition;
|
|
92
|
+
autoClose?: number;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function Alert({
|
|
96
|
+
className,
|
|
97
|
+
variant = 'default',
|
|
98
|
+
children,
|
|
99
|
+
open: controlledOpen,
|
|
100
|
+
defaultOpen = true,
|
|
101
|
+
onOpenChange,
|
|
102
|
+
animated = true,
|
|
103
|
+
transition = alertDefaultTransition,
|
|
104
|
+
autoClose,
|
|
105
|
+
...props
|
|
106
|
+
}: AlertProps) {
|
|
107
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
108
|
+
const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
|
109
|
+
|
|
110
|
+
const handleClose = useCallback(() => {
|
|
111
|
+
if (controlledOpen === undefined) {
|
|
112
|
+
setInternalOpen(false);
|
|
113
|
+
}
|
|
114
|
+
onOpenChange?.(false);
|
|
115
|
+
}, [controlledOpen, onOpenChange]);
|
|
116
|
+
|
|
117
|
+
const handleCloseRef = useRef(handleClose);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
handleCloseRef.current = handleClose;
|
|
120
|
+
}, [handleClose]);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (autoClose && isOpen && autoClose > 0) {
|
|
124
|
+
const timeoutId = setTimeout(() => {
|
|
125
|
+
handleCloseRef.current();
|
|
126
|
+
}, autoClose);
|
|
127
|
+
|
|
128
|
+
return () => {
|
|
129
|
+
clearTimeout(timeoutId);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}, [autoClose, isOpen]);
|
|
133
|
+
|
|
134
|
+
const contextValue: AlertContextValue = {
|
|
135
|
+
isOpen,
|
|
136
|
+
onClose: handleClose,
|
|
137
|
+
variant,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (!animated) {
|
|
141
|
+
if (!isOpen) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<AlertContext.Provider value={contextValue}>
|
|
147
|
+
<div className="w-full flex items-start gap-4">
|
|
148
|
+
<div
|
|
149
|
+
data-slot="alert-line"
|
|
150
|
+
className={cn(alertLineVariants({variant}))}
|
|
151
|
+
aria-hidden="true"
|
|
152
|
+
/>
|
|
153
|
+
<div
|
|
154
|
+
data-slot="alert"
|
|
155
|
+
role="alert"
|
|
156
|
+
className={cn(alertVariants({variant}), className)}
|
|
157
|
+
{...props}
|
|
158
|
+
>
|
|
159
|
+
{children}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</AlertContext.Provider>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
55
165
|
|
|
56
|
-
function Alert({className, variant, children, ...props}: AlertProps) {
|
|
57
166
|
return (
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
167
|
+
<AnimatePresence>
|
|
168
|
+
{isOpen && (
|
|
169
|
+
<motion.div
|
|
170
|
+
key="alert"
|
|
171
|
+
className="w-full flex items-start gap-4"
|
|
172
|
+
initial={{opacity: 0}}
|
|
173
|
+
animate={{opacity: 1}}
|
|
174
|
+
exit={{opacity: 0}}
|
|
175
|
+
transition={transition}
|
|
176
|
+
>
|
|
177
|
+
<AlertContext.Provider value={contextValue}>
|
|
178
|
+
<div
|
|
179
|
+
data-slot="alert-line"
|
|
180
|
+
className={cn(alertLineVariants({variant}))}
|
|
181
|
+
aria-hidden="true"
|
|
182
|
+
/>
|
|
183
|
+
<div
|
|
184
|
+
data-slot="alert"
|
|
185
|
+
role="alert"
|
|
186
|
+
className={cn(alertVariants({variant}), className)}
|
|
187
|
+
{...props}
|
|
188
|
+
>
|
|
189
|
+
{children}
|
|
190
|
+
</div>
|
|
191
|
+
</AlertContext.Provider>
|
|
192
|
+
</motion.div>
|
|
193
|
+
)}
|
|
194
|
+
</AnimatePresence>
|
|
69
195
|
);
|
|
70
196
|
}
|
|
71
197
|
|
|
@@ -122,9 +248,18 @@ function AlertAction({className, ...props}: ComponentProps<'button'>) {
|
|
|
122
248
|
|
|
123
249
|
function AlertClose({
|
|
124
250
|
className,
|
|
125
|
-
variant
|
|
251
|
+
variant: variantProp,
|
|
252
|
+
onClick,
|
|
126
253
|
...props
|
|
127
254
|
}: ComponentProps<'button'> & VariantProps<typeof closeIconVariants>) {
|
|
255
|
+
const {onClose, variant: contextVariant} = useAlertContext();
|
|
256
|
+
const variant = variantProp ?? contextVariant ?? 'default';
|
|
257
|
+
|
|
258
|
+
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
|
259
|
+
onClose();
|
|
260
|
+
onClick?.(e);
|
|
261
|
+
};
|
|
262
|
+
|
|
128
263
|
return (
|
|
129
264
|
<button
|
|
130
265
|
data-slot="alert-close"
|
|
@@ -134,6 +269,7 @@ function AlertClose({
|
|
|
134
269
|
className,
|
|
135
270
|
)}
|
|
136
271
|
aria-label="Close"
|
|
272
|
+
onClick={handleClick}
|
|
137
273
|
{...props}
|
|
138
274
|
>
|
|
139
275
|
<Icon name="close" className={cn(closeIconVariants({variant}))} />
|
|
@@ -141,4 +277,15 @@ function AlertClose({
|
|
|
141
277
|
);
|
|
142
278
|
}
|
|
143
279
|
|
|
144
|
-
export {
|
|
280
|
+
export {
|
|
281
|
+
Alert,
|
|
282
|
+
AlertContent,
|
|
283
|
+
AlertTitle,
|
|
284
|
+
AlertDescription,
|
|
285
|
+
AlertActions,
|
|
286
|
+
AlertAction,
|
|
287
|
+
AlertClose,
|
|
288
|
+
alertDefaultTransition,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export type {AlertProps};
|
|
@@ -5,7 +5,7 @@ import type {ComponentProps} from 'react';
|
|
|
5
5
|
import {cn} from 'utils/cn';
|
|
6
6
|
|
|
7
7
|
export const buttonLinkVariants = cva(
|
|
8
|
-
'inline-flex items-center justify-center gap-4 whitespace-nowrap transition-colors disabled:pointer-events-none outline-none font-medium',
|
|
8
|
+
'inline-flex items-center justify-center gap-4 whitespace-nowrap transition-colors cursor-pointer disabled:pointer-events-none outline-none font-medium',
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
@@ -5,7 +5,7 @@ import type {ComponentProps} from 'react';
|
|
|
5
5
|
import {cn} from 'utils/cn';
|
|
6
6
|
|
|
7
7
|
export const buttonVariants = cva(
|
|
8
|
-
'rounded-6 inline-flex items-center justify-center whitespace-nowrap transition-colors disabled:pointer-events-none shrink-0 outline-none',
|
|
8
|
+
'rounded-6 inline-flex items-center justify-center whitespace-nowrap transition-colors cursor-pointer disabled:pointer-events-none shrink-0 outline-none',
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
@@ -76,6 +76,7 @@ export function Button({
|
|
|
76
76
|
disabled={disabled || isLoading}
|
|
77
77
|
aria-busy={isLoading}
|
|
78
78
|
aria-live={isLoading ? 'polite' : undefined}
|
|
79
|
+
{...(asChild ? {'aria-disabled': disabled || isLoading} : {})}
|
|
79
80
|
{...props}
|
|
80
81
|
>
|
|
81
82
|
{isLoading ? (
|
|
@@ -5,7 +5,7 @@ import type {ComponentProps} from 'react';
|
|
|
5
5
|
import {cn} from 'utils/cn';
|
|
6
6
|
|
|
7
7
|
export const iconButtonVariants = cva(
|
|
8
|
-
'inline-flex items-center justify-center whitespace-nowrap transition-colors disabled:pointer-events-none shrink-0 outline-none',
|
|
8
|
+
'inline-flex items-center justify-center whitespace-nowrap transition-colors cursor-pointer disabled:pointer-events-none shrink-0 outline-none',
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
@@ -80,6 +80,7 @@ export function IconButton({
|
|
|
80
80
|
disabled={disabled || isLoading}
|
|
81
81
|
aria-busy={isLoading}
|
|
82
82
|
aria-live={isLoading ? 'polite' : undefined}
|
|
83
|
+
{...(asChild ? {'aria-disabled': disabled || isLoading} : {})}
|
|
83
84
|
{...props}
|
|
84
85
|
>
|
|
85
86
|
{isLoading ? (
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {buttonLinkVariants} from 'components/button/button-link';
|
|
1
2
|
import {Label} from 'components/label';
|
|
2
3
|
import {type ReactNode, useId} from 'react';
|
|
3
4
|
import {cn} from 'utils/cn';
|
|
@@ -58,10 +59,12 @@ export function CheckboxLinks({
|
|
|
58
59
|
{link.href ? (
|
|
59
60
|
<a
|
|
60
61
|
href={link.href}
|
|
62
|
+
target="_blank"
|
|
63
|
+
rel="noopener noreferrer"
|
|
61
64
|
onClick={link.onClick}
|
|
62
65
|
className={cn(
|
|
63
66
|
'text-sm leading-20 font-medium text-foreground-highlight-interactive',
|
|
64
|
-
'hover:text-foreground-highlight-interactive-hover',
|
|
67
|
+
'hover:text-foreground-highlight-interactive-hover transition-colors',
|
|
65
68
|
linkClassName,
|
|
66
69
|
)}
|
|
67
70
|
>
|
|
@@ -72,8 +75,7 @@ export function CheckboxLinks({
|
|
|
72
75
|
type="button"
|
|
73
76
|
onClick={link.onClick}
|
|
74
77
|
className={cn(
|
|
75
|
-
'
|
|
76
|
-
'hover:text-foreground-highlight-interactive-hover',
|
|
78
|
+
buttonLinkVariants({variant: 'interactive', size: 'sm'}),
|
|
77
79
|
linkClassName,
|
|
78
80
|
)}
|
|
79
81
|
>
|
|
@@ -356,16 +356,32 @@ export const CheckboxLinksStory: StoryObj = {
|
|
|
356
356
|
id="checkbox-links-default"
|
|
357
357
|
label="Accept policies"
|
|
358
358
|
links={[
|
|
359
|
-
{label: 'Terms of use', href: '
|
|
360
|
-
{
|
|
359
|
+
{label: 'Terms of use', href: 'https://www.shipfox.io/legal/terms-of-service'},
|
|
360
|
+
{
|
|
361
|
+
label: 'Privacy Policy',
|
|
362
|
+
onClick: () =>
|
|
363
|
+
window.open(
|
|
364
|
+
'https://www.shipfox.io/legal/privacy-policy',
|
|
365
|
+
'_blank',
|
|
366
|
+
'noopener,noreferrer',
|
|
367
|
+
),
|
|
368
|
+
},
|
|
361
369
|
]}
|
|
362
370
|
/>
|
|
363
371
|
<CheckboxLinks
|
|
364
372
|
id="checkbox-links-checked"
|
|
365
373
|
label="Accept policies"
|
|
366
374
|
links={[
|
|
367
|
-
{label: 'Terms of use', href: '
|
|
368
|
-
{
|
|
375
|
+
{label: 'Terms of use', href: 'https://www.shipfox.io/legal/terms-of-service'},
|
|
376
|
+
{
|
|
377
|
+
label: 'Privacy Policy',
|
|
378
|
+
onClick: () =>
|
|
379
|
+
window.open(
|
|
380
|
+
'https://www.shipfox.io/legal/privacy-policy',
|
|
381
|
+
'_blank',
|
|
382
|
+
'noopener,noreferrer',
|
|
383
|
+
),
|
|
384
|
+
},
|
|
369
385
|
]}
|
|
370
386
|
checked
|
|
371
387
|
/>
|
|
@@ -32,11 +32,26 @@ import {
|
|
|
32
32
|
ThunderIcon,
|
|
33
33
|
XCircleSolidIcon,
|
|
34
34
|
} from './custom';
|
|
35
|
+
import {remixiconMap} from './remixicon-registry';
|
|
35
36
|
|
|
36
|
-
const
|
|
37
|
+
const commonRemixicons = {
|
|
38
|
+
addLine: RiAddLine,
|
|
39
|
+
close: RiCloseLine,
|
|
40
|
+
check: RiCheckLine,
|
|
41
|
+
copy: RiFileCopyLine,
|
|
42
|
+
info: RiInformationFill,
|
|
43
|
+
imageAdd: RiImageAddFill,
|
|
44
|
+
chevronRight: RiArrowRightSLine,
|
|
45
|
+
homeSmile: RiHomeSmileFill,
|
|
46
|
+
money: RiMoneyDollarCircleLine,
|
|
37
47
|
google: RiGoogleFill,
|
|
38
48
|
microsoft: RiMicrosoftFill,
|
|
39
49
|
github: RiGithubFill,
|
|
50
|
+
subtractLine: RiSubtractLine,
|
|
51
|
+
bookOpen: RiBookOpenFill,
|
|
52
|
+
} as const satisfies Record<string, RemixiconComponentType>;
|
|
53
|
+
|
|
54
|
+
const customIconsMap = {
|
|
40
55
|
shipfox: ShipfoxLogo,
|
|
41
56
|
slack: SlackLogo,
|
|
42
57
|
stripe: StripeLogo,
|
|
@@ -51,23 +66,18 @@ const iconsMap = {
|
|
|
51
66
|
spinner: SpinnerIcon,
|
|
52
67
|
thunder: ThunderIcon,
|
|
53
68
|
xCircleSolid: XCircleSolidIcon,
|
|
54
|
-
addLine: RiAddLine,
|
|
55
|
-
bookOpen: RiBookOpenFill,
|
|
56
|
-
check: RiCheckLine,
|
|
57
|
-
chevronRight: RiArrowRightSLine,
|
|
58
|
-
close: RiCloseLine,
|
|
59
|
-
copy: RiFileCopyLine,
|
|
60
|
-
homeSmile: RiHomeSmileFill,
|
|
61
|
-
imageAdd: RiImageAddFill,
|
|
62
|
-
info: RiInformationFill,
|
|
63
|
-
money: RiMoneyDollarCircleLine,
|
|
64
|
-
subtractLine: RiSubtractLine,
|
|
65
69
|
} as const satisfies Record<string, RemixiconComponentType>;
|
|
66
70
|
|
|
71
|
+
const iconsMap = {
|
|
72
|
+
...remixiconMap,
|
|
73
|
+
...commonRemixicons,
|
|
74
|
+
...customIconsMap,
|
|
75
|
+
} as Record<string, RemixiconComponentType> & typeof customIconsMap;
|
|
76
|
+
|
|
67
77
|
export type IconName = keyof typeof iconsMap;
|
|
68
78
|
export const iconNames = Object.keys(iconsMap) as IconName[];
|
|
69
79
|
|
|
70
|
-
type BaseIconProps = ComponentProps<
|
|
80
|
+
type BaseIconProps = ComponentProps<RemixiconComponentType>;
|
|
71
81
|
type IconProps = {name: IconName} & Omit<BaseIconProps, 'name'>;
|
|
72
82
|
|
|
73
83
|
export function Icon({name, ...props}: IconProps) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {RemixiconComponentType} from '@remixicon/react';
|
|
2
|
+
import * as RemixIcons from '@remixicon/react';
|
|
3
|
+
|
|
4
|
+
const remixiconEntries = Object.entries(RemixIcons).filter(
|
|
5
|
+
([key, value]) => key.startsWith('Ri') && typeof value === 'function',
|
|
6
|
+
) as Array<[string, RemixiconComponentType]>;
|
|
7
|
+
|
|
8
|
+
function iconNameToKey(iconName: string): string {
|
|
9
|
+
const withoutPrefix = iconName.slice(2);
|
|
10
|
+
return withoutPrefix.charAt(0).toLowerCase() + withoutPrefix.slice(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const remixiconMapEntries = remixiconEntries.map(([name, component]) => [
|
|
14
|
+
iconNameToKey(name),
|
|
15
|
+
component,
|
|
16
|
+
]) as Array<[string, RemixiconComponentType]>;
|
|
17
|
+
|
|
18
|
+
export const remixiconMap = Object.fromEntries(remixiconMapEntries) as Record<
|
|
19
|
+
string,
|
|
20
|
+
RemixiconComponentType
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export type RemixIconName = keyof typeof remixiconMap;
|
|
24
|
+
export const remixiconNames = Object.keys(remixiconMap) as RemixIconName[];
|