@shipfox/react-ui 0.12.0 → 0.14.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 +5 -5
- package/.turbo/turbo-check.log +2 -2
- package/.turbo/turbo-type.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/components/badge/index.d.ts +4 -4
- package/dist/components/badge/index.d.ts.map +1 -1
- package/dist/components/badge/index.js +4 -4
- package/dist/components/badge/index.js.map +1 -1
- package/dist/components/code-block/code-block-footer.d.ts.map +1 -1
- package/dist/components/code-block/code-block-footer.js +11 -5
- package/dist/components/code-block/code-block-footer.js.map +1 -1
- package/dist/components/confetti/confetti.d.ts +21 -0
- package/dist/components/confetti/confetti.d.ts.map +1 -0
- package/dist/components/confetti/confetti.js +101 -0
- package/dist/components/confetti/confetti.js.map +1 -0
- package/dist/components/confetti/confetti.stories.js +41 -0
- package/dist/components/confetti/confetti.stories.js.map +1 -0
- package/dist/components/confetti/index.d.ts +2 -0
- package/dist/components/confetti/index.d.ts.map +1 -0
- package/dist/components/confetti/index.js +3 -0
- package/dist/components/confetti/index.js.map +1 -0
- package/dist/components/dropdown-menu/index.d.ts +1 -2
- package/dist/components/dropdown-menu/index.d.ts.map +1 -1
- package/dist/components/dropdown-menu/index.js +1 -1
- package/dist/components/dropdown-menu/index.js.map +1 -1
- package/dist/components/icon/icon.d.ts +3 -2
- package/dist/components/icon/icon.d.ts.map +1 -1
- package/dist/components/icon/icon.js +7 -2
- package/dist/components/icon/icon.js.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/modal/index.d.ts +1 -2
- package/dist/components/modal/index.d.ts.map +1 -1
- package/dist/components/modal/index.js +1 -1
- package/dist/components/modal/index.js.map +1 -1
- package/dist/components/modal/modal.d.ts +2 -1
- package/dist/components/modal/modal.d.ts.map +1 -1
- package/dist/components/modal/modal.js +5 -3
- package/dist/components/modal/modal.js.map +1 -1
- package/dist/components/modal/modal.stories.js +228 -167
- package/dist/components/modal/modal.stories.js.map +1 -1
- package/dist/components/shiny-text/index.d.ts +2 -0
- package/dist/components/shiny-text/index.d.ts.map +1 -0
- package/dist/components/shiny-text/index.js +3 -0
- package/dist/components/shiny-text/index.js.map +1 -0
- package/dist/components/shiny-text/shiny-text.d.ts +10 -0
- package/dist/components/shiny-text/shiny-text.d.ts.map +1 -0
- package/dist/components/shiny-text/shiny-text.js +17 -0
- package/dist/components/shiny-text/shiny-text.js.map +1 -0
- package/dist/components/tabs/index.d.ts +2 -0
- package/dist/components/tabs/index.d.ts.map +1 -0
- package/dist/components/tabs/index.js +3 -0
- package/dist/components/tabs/index.js.map +1 -0
- package/dist/components/tabs/tabs.d.ts +50 -0
- package/dist/components/tabs/tabs.d.ts.map +1 -0
- package/dist/components/tabs/tabs.js +243 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/components/tabs/tabs.stories.js +179 -0
- package/dist/components/tabs/tabs.stories.js.map +1 -0
- package/dist/components/toast/index.d.ts +2 -2
- package/dist/components/toast/index.d.ts.map +1 -1
- package/dist/components/toast/index.js +2 -2
- package/dist/components/toast/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/debounce.d.ts +2 -0
- package/dist/utils/debounce.d.ts.map +1 -0
- package/dist/utils/debounce.js +13 -0
- package/dist/utils/debounce.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/index.css +34 -0
- package/package.json +4 -1
- package/src/components/badge/index.ts +4 -4
- package/src/components/code-block/code-block-footer.tsx +12 -2
- package/src/components/confetti/confetti.stories.tsx +38 -0
- package/src/components/confetti/confetti.tsx +140 -0
- package/src/components/confetti/index.ts +1 -0
- package/src/components/dropdown-menu/index.ts +1 -29
- package/src/components/icon/icon.tsx +7 -3
- package/src/components/index.ts +4 -0
- package/src/components/modal/index.ts +1 -23
- package/src/components/modal/modal.stories.tsx +60 -6
- package/src/components/modal/modal.tsx +4 -2
- package/src/components/shiny-text/index.ts +1 -0
- package/src/components/shiny-text/shiny-text.tsx +21 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.stories.tsx +100 -0
- package/src/components/tabs/tabs.tsx +380 -0
- package/src/components/toast/index.ts +2 -2
- package/src/index.ts +1 -0
- package/src/utils/debounce.ts +15 -0
- package/src/utils/index.ts +1 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {Slot} from '@radix-ui/react-slot';
|
|
2
2
|
import {Icon} from 'components/icon/icon';
|
|
3
|
+
import {ShinyText} from 'components/shiny-text';
|
|
3
4
|
import {Text} from 'components/typography';
|
|
4
5
|
import type {ComponentProps, HTMLAttributes, ReactNode} from 'react';
|
|
6
|
+
import {ShipfoxLoader} from 'shipfox-loader-react';
|
|
5
7
|
import {cn} from 'utils/cn';
|
|
6
8
|
|
|
7
9
|
export type CodeBlockFooterProps = HTMLAttributes<HTMLDivElement> & {
|
|
@@ -27,7 +29,7 @@ export function CodeBlockFooter({
|
|
|
27
29
|
const defaultIcon =
|
|
28
30
|
icon ??
|
|
29
31
|
(state === 'running' ? (
|
|
30
|
-
<
|
|
32
|
+
<ShipfoxLoader size={20} animation="circular" color="white" background="dark" />
|
|
31
33
|
) : (
|
|
32
34
|
<Icon
|
|
33
35
|
name="checkCircleSolid"
|
|
@@ -57,7 +59,15 @@ export function CodeBlockFooter({
|
|
|
57
59
|
<CodeBlockFooterIcon className="text-tag-success-icon">{defaultIcon}</CodeBlockFooterIcon>
|
|
58
60
|
{(message || description) && (
|
|
59
61
|
<CodeBlockFooterContent>
|
|
60
|
-
{message &&
|
|
62
|
+
{message && (
|
|
63
|
+
<CodeBlockFooterMessage>
|
|
64
|
+
{state === 'running' && typeof message === 'string' ? (
|
|
65
|
+
<ShinyText text={message} speed={3} />
|
|
66
|
+
) : (
|
|
67
|
+
message
|
|
68
|
+
)}
|
|
69
|
+
</CodeBlockFooterMessage>
|
|
70
|
+
)}
|
|
61
71
|
{description && <CodeBlockFooterDescription>{description}</CodeBlockFooterDescription>}
|
|
62
72
|
</CodeBlockFooterContent>
|
|
63
73
|
)}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {Confetti, ConfettiButton} from './confetti';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Components/Confetti',
|
|
6
|
+
component: Confetti,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
},
|
|
11
|
+
} satisfies Meta<typeof Confetti>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<div className="flex h-400 w-600 items-center justify-center rounded-16 bg-background-subtle-base">
|
|
19
|
+
<ConfettiButton>Click for Confetti!</ConfettiButton>
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const WithOptions: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<div className="flex h-400 w-600 items-center justify-center rounded-16 bg-background-subtle-base">
|
|
27
|
+
<ConfettiButton
|
|
28
|
+
options={{
|
|
29
|
+
particleCount: 150,
|
|
30
|
+
spread: 60,
|
|
31
|
+
colors: ['#ff6b6b', '#4ecdc4', '#ffe66d', '#95e1d3'],
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
Custom Confetti Button
|
|
35
|
+
</ConfettiButton>
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GlobalOptions as ConfettiGlobalOptions,
|
|
3
|
+
CreateTypes as ConfettiInstance,
|
|
4
|
+
Options as ConfettiOptions,
|
|
5
|
+
} from 'canvas-confetti';
|
|
6
|
+
import confetti from 'canvas-confetti';
|
|
7
|
+
import {Button} from 'components/button';
|
|
8
|
+
import type {ComponentProps, ReactNode} from 'react';
|
|
9
|
+
import {
|
|
10
|
+
createContext,
|
|
11
|
+
forwardRef,
|
|
12
|
+
useCallback,
|
|
13
|
+
useEffect,
|
|
14
|
+
useImperativeHandle,
|
|
15
|
+
useMemo,
|
|
16
|
+
useRef,
|
|
17
|
+
} from 'react';
|
|
18
|
+
|
|
19
|
+
type ConfettiApi = {
|
|
20
|
+
fire: (options?: ConfettiOptions) => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const ConfettiContext = createContext<ConfettiApi | null>(null);
|
|
24
|
+
|
|
25
|
+
export type ConfettiRef = ConfettiApi | null;
|
|
26
|
+
|
|
27
|
+
export type ConfettiProps = ComponentProps<'canvas'> & {
|
|
28
|
+
options?: ConfettiOptions;
|
|
29
|
+
globalOptions?: ConfettiGlobalOptions;
|
|
30
|
+
manualstart?: boolean;
|
|
31
|
+
children?: ReactNode;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ConfettiComponent = forwardRef<ConfettiRef, ConfettiProps>(
|
|
35
|
+
(
|
|
36
|
+
{
|
|
37
|
+
options,
|
|
38
|
+
globalOptions = {resize: true, useWorker: true},
|
|
39
|
+
manualstart = false,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
},
|
|
43
|
+
ref,
|
|
44
|
+
) => {
|
|
45
|
+
const instanceRef = useRef<ConfettiInstance | null>(null);
|
|
46
|
+
const hasAutoFiredRef = useRef<boolean>(false);
|
|
47
|
+
const optionsRef = useRef<ConfettiOptions | undefined>(options);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
optionsRef.current = options;
|
|
51
|
+
}, [options]);
|
|
52
|
+
|
|
53
|
+
const canvasRef = useCallback(
|
|
54
|
+
(node: HTMLCanvasElement | null) => {
|
|
55
|
+
if (node !== null) {
|
|
56
|
+
if (instanceRef.current) {
|
|
57
|
+
instanceRef.current.reset();
|
|
58
|
+
}
|
|
59
|
+
instanceRef.current = confetti.create(node, {
|
|
60
|
+
...globalOptions,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
if (instanceRef.current) {
|
|
64
|
+
instanceRef.current.reset();
|
|
65
|
+
instanceRef.current = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[globalOptions],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const fire = useCallback(async (opts: ConfettiOptions = {}) => {
|
|
73
|
+
try {
|
|
74
|
+
await instanceRef.current?.({...optionsRef.current, ...opts});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
// biome-ignore lint/suspicious/noConsole: we need to log the error
|
|
77
|
+
console.error('Confetti error:', error);
|
|
78
|
+
}
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const api = useMemo<ConfettiApi>(
|
|
82
|
+
() => ({
|
|
83
|
+
fire,
|
|
84
|
+
}),
|
|
85
|
+
[fire],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
useImperativeHandle(ref, () => api, [api]);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!manualstart && !hasAutoFiredRef.current && instanceRef.current) {
|
|
92
|
+
hasAutoFiredRef.current = true;
|
|
93
|
+
void instanceRef.current(optionsRef.current);
|
|
94
|
+
}
|
|
95
|
+
}, [manualstart]);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<ConfettiContext.Provider value={api}>
|
|
99
|
+
<canvas ref={canvasRef} {...props} />
|
|
100
|
+
{children}
|
|
101
|
+
</ConfettiContext.Provider>
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
ConfettiComponent.displayName = 'Confetti';
|
|
107
|
+
|
|
108
|
+
export const Confetti = ConfettiComponent;
|
|
109
|
+
|
|
110
|
+
export type ConfettiButtonProps = ComponentProps<'button'> & {
|
|
111
|
+
options?: ConfettiOptions & ConfettiGlobalOptions & {canvas?: HTMLCanvasElement};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export function ConfettiButton({options, onClick, children, ...props}: ConfettiButtonProps) {
|
|
115
|
+
const handleClick: ComponentProps<'button'>['onClick'] = async (event) => {
|
|
116
|
+
try {
|
|
117
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
118
|
+
const x = rect.left + rect.width / 2;
|
|
119
|
+
const y = rect.top + rect.height / 2;
|
|
120
|
+
await confetti({
|
|
121
|
+
...options,
|
|
122
|
+
origin: {
|
|
123
|
+
x: x / window.innerWidth,
|
|
124
|
+
y: y / window.innerHeight,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// biome-ignore lint/suspicious/noConsole: we need to log the error
|
|
129
|
+
console.error('Confetti button error:', error);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onClick?.(event);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Button onClick={handleClick} {...props}>
|
|
137
|
+
{children}
|
|
138
|
+
</Button>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './confetti';
|
|
@@ -1,29 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
DropdownMenuCheckboxItemProps,
|
|
3
|
-
DropdownMenuContentProps,
|
|
4
|
-
DropdownMenuItemProps,
|
|
5
|
-
DropdownMenuLabelProps,
|
|
6
|
-
DropdownMenuRadioItemProps,
|
|
7
|
-
DropdownMenuShortcutProps,
|
|
8
|
-
DropdownMenuSubTriggerProps,
|
|
9
|
-
} from './dropdown-menu';
|
|
10
|
-
export {
|
|
11
|
-
DropdownMenu,
|
|
12
|
-
DropdownMenuCheckboxItem,
|
|
13
|
-
DropdownMenuContent,
|
|
14
|
-
DropdownMenuGroup,
|
|
15
|
-
DropdownMenuItem,
|
|
16
|
-
DropdownMenuLabel,
|
|
17
|
-
DropdownMenuPortal,
|
|
18
|
-
DropdownMenuRadioGroup,
|
|
19
|
-
DropdownMenuRadioItem,
|
|
20
|
-
DropdownMenuSeparator,
|
|
21
|
-
DropdownMenuShortcut,
|
|
22
|
-
DropdownMenuSub,
|
|
23
|
-
DropdownMenuSubContent,
|
|
24
|
-
DropdownMenuSubTrigger,
|
|
25
|
-
DropdownMenuTrigger,
|
|
26
|
-
dropdownMenuContentVariants,
|
|
27
|
-
dropdownMenuItemVariants,
|
|
28
|
-
dropdownMenuLabelVariants,
|
|
29
|
-
} from './dropdown-menu';
|
|
1
|
+
export * from './dropdown-menu';
|
|
@@ -78,9 +78,13 @@ export type IconName = keyof typeof iconsMap;
|
|
|
78
78
|
export const iconNames = Object.keys(iconsMap) as IconName[];
|
|
79
79
|
|
|
80
80
|
type BaseIconProps = ComponentProps<RemixiconComponentType>;
|
|
81
|
-
type IconProps = {name: IconName} & Omit<
|
|
81
|
+
type IconProps = {name: IconName; size?: number | string} & Omit<
|
|
82
|
+
BaseIconProps,
|
|
83
|
+
'name' | 'size' | 'width' | 'height'
|
|
84
|
+
>;
|
|
82
85
|
|
|
83
|
-
export function Icon({name, ...props}: IconProps) {
|
|
86
|
+
export function Icon({name, size, ...props}: IconProps) {
|
|
84
87
|
const IconComponent = iconsMap[name];
|
|
85
|
-
|
|
88
|
+
const svgProps = size && typeof size === 'number' ? {...props, width: size, height: size} : props;
|
|
89
|
+
return <IconComponent {...svgProps} />;
|
|
86
90
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './button';
|
|
|
5
5
|
export * from './calendar';
|
|
6
6
|
export * from './checkbox';
|
|
7
7
|
export * from './code-block';
|
|
8
|
+
export * from './confetti';
|
|
8
9
|
export * from './date-picker';
|
|
9
10
|
export * from './date-time-range-picker';
|
|
10
11
|
export * from './dot-grid';
|
|
@@ -17,7 +18,10 @@ export * from './input';
|
|
|
17
18
|
export * from './item';
|
|
18
19
|
export * from './label';
|
|
19
20
|
export * from './modal';
|
|
21
|
+
export * from './moving-border';
|
|
20
22
|
export * from './popover';
|
|
23
|
+
export * from './shiny-text';
|
|
24
|
+
export * from './tabs';
|
|
21
25
|
export * from './textarea';
|
|
22
26
|
export * from './theme';
|
|
23
27
|
export * from './toast';
|
|
@@ -1,23 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
ModalContentProps,
|
|
3
|
-
ModalDescriptionProps,
|
|
4
|
-
ModalHeaderProps,
|
|
5
|
-
ModalOverlayProps,
|
|
6
|
-
ModalTitleProps,
|
|
7
|
-
} from './modal';
|
|
8
|
-
export {
|
|
9
|
-
Modal,
|
|
10
|
-
ModalBody,
|
|
11
|
-
ModalClose,
|
|
12
|
-
ModalContent,
|
|
13
|
-
ModalDescription,
|
|
14
|
-
ModalFooter,
|
|
15
|
-
ModalHeader,
|
|
16
|
-
ModalOverlay,
|
|
17
|
-
ModalPortal,
|
|
18
|
-
ModalTitle,
|
|
19
|
-
ModalTrigger,
|
|
20
|
-
modalContentVariants,
|
|
21
|
-
modalDefaultTransition,
|
|
22
|
-
modalOverlayVariants,
|
|
23
|
-
} from './modal';
|
|
1
|
+
export * from './modal';
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
CodeBlockHeader,
|
|
15
15
|
CodeBlockItem,
|
|
16
16
|
} from 'components/code-block';
|
|
17
|
+
import {Confetti, type ConfettiRef} from 'components/confetti';
|
|
17
18
|
import {DatePicker} from 'components/date-picker';
|
|
18
19
|
import {DynamicItem} from 'components/dynamic-item';
|
|
19
20
|
import {Icon} from 'components/icon';
|
|
@@ -22,7 +23,7 @@ import {ItemTitle} from 'components/item';
|
|
|
22
23
|
import {Label} from 'components/label';
|
|
23
24
|
import {MovingBorder} from 'components/moving-border';
|
|
24
25
|
import {Text} from 'components/typography';
|
|
25
|
-
import {useState} from 'react';
|
|
26
|
+
import {useEffect, useRef, useState} from 'react';
|
|
26
27
|
import {cn} from 'utils/cn';
|
|
27
28
|
import illustration2 from '../../assets/illustration-2.svg';
|
|
28
29
|
import illustrationBg from '../../assets/illustration-gradient.svg';
|
|
@@ -140,7 +141,7 @@ export const ImportForm: Story = {
|
|
|
140
141
|
<ModalTrigger asChild>
|
|
141
142
|
<Button>Import past jobs from GitHub</Button>
|
|
142
143
|
</ModalTrigger>
|
|
143
|
-
<ModalContent aria-describedby={undefined}>
|
|
144
|
+
<ModalContent aria-describedby={undefined} overlayClassName="bg-background-modal-overlay">
|
|
144
145
|
<ModalTitle className="sr-only">Import past jobs from GitHub</ModalTitle>
|
|
145
146
|
<ModalHeader title="Import past jobs from GitHub" />
|
|
146
147
|
<ModalBody className="gap-20">
|
|
@@ -214,14 +215,59 @@ export const GithubActions: Story = {
|
|
|
214
215
|
},
|
|
215
216
|
render: () => {
|
|
216
217
|
const [open, setOpen] = useState(false);
|
|
218
|
+
const [footerState, setFooterState] = useState<'running' | 'done'>('running');
|
|
219
|
+
const confettiRef = useRef<ConfettiRef>(null);
|
|
220
|
+
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
if (open && footerState === 'running') {
|
|
223
|
+
const timer = setTimeout(() => {
|
|
224
|
+
setFooterState('done');
|
|
225
|
+
}, 3000);
|
|
226
|
+
|
|
227
|
+
return () => {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}, [open, footerState]);
|
|
232
|
+
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (footerState === 'done' && open) {
|
|
235
|
+
const timer = setTimeout(() => {
|
|
236
|
+
if (confettiRef.current) {
|
|
237
|
+
void confettiRef.current.fire();
|
|
238
|
+
}
|
|
239
|
+
}, 200);
|
|
240
|
+
|
|
241
|
+
return () => {
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}, [footerState, open]);
|
|
246
|
+
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
if (!open) {
|
|
249
|
+
setFooterState('running');
|
|
250
|
+
}
|
|
251
|
+
}, [open]);
|
|
217
252
|
|
|
218
253
|
return (
|
|
219
254
|
<div className="flex h-[50vh] w-[calc(100vw/2)] items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip">
|
|
255
|
+
<Confetti
|
|
256
|
+
ref={confettiRef}
|
|
257
|
+
manualstart
|
|
258
|
+
options={{
|
|
259
|
+
particleCount: 200,
|
|
260
|
+
spread: 100,
|
|
261
|
+
colors: ['#ff6b6b', '#4ecdc4', '#ffe66d', '#95e1d3'],
|
|
262
|
+
}}
|
|
263
|
+
className="fixed inset-0 pointer-events-none z-100"
|
|
264
|
+
style={{width: '100%', height: '100%'}}
|
|
265
|
+
/>
|
|
220
266
|
<Modal open={open} onOpenChange={setOpen}>
|
|
221
267
|
<ModalTrigger asChild>
|
|
222
268
|
<Button>Run GitHub Actions on Shipfox</Button>
|
|
223
269
|
</ModalTrigger>
|
|
224
|
-
<ModalContent aria-describedby={undefined}>
|
|
270
|
+
<ModalContent aria-describedby={undefined} overlayClassName="bg-background-modal-overlay">
|
|
225
271
|
<ModalTitle className="sr-only">Run GitHub Actions on Shipfox</ModalTitle>
|
|
226
272
|
<ModalHeader title="Run GitHub Actions on Shipfox" />
|
|
227
273
|
<ModalBody className="gap-32">
|
|
@@ -316,9 +362,17 @@ export const GithubActions: Story = {
|
|
|
316
362
|
)}
|
|
317
363
|
</CodeBlockBody>
|
|
318
364
|
<CodeBlockFooter
|
|
319
|
-
state=
|
|
320
|
-
message=
|
|
321
|
-
|
|
365
|
+
state={footerState}
|
|
366
|
+
message={
|
|
367
|
+
footerState === 'running'
|
|
368
|
+
? 'Waiting for Shipfox runner event…'
|
|
369
|
+
: 'Runners connected!'
|
|
370
|
+
}
|
|
371
|
+
description={
|
|
372
|
+
footerState === 'running'
|
|
373
|
+
? 'This usually takes 30-60 seconds after you commit the workflow file.'
|
|
374
|
+
: ''
|
|
375
|
+
}
|
|
322
376
|
/>
|
|
323
377
|
</CodeBlock>
|
|
324
378
|
</div>
|
|
@@ -125,6 +125,7 @@ function ModalOverlay({
|
|
|
125
125
|
type ModalContentProps = ComponentProps<typeof DialogPrimitive.Content> & {
|
|
126
126
|
animated?: boolean;
|
|
127
127
|
transition?: Transition;
|
|
128
|
+
overlayClassName?: string;
|
|
128
129
|
};
|
|
129
130
|
|
|
130
131
|
function ModalContent({
|
|
@@ -132,6 +133,7 @@ function ModalContent({
|
|
|
132
133
|
children,
|
|
133
134
|
animated = true,
|
|
134
135
|
transition = modalDefaultTransition,
|
|
136
|
+
overlayClassName,
|
|
135
137
|
...props
|
|
136
138
|
}: ModalContentProps) {
|
|
137
139
|
const {isDesktop} = useModalContext();
|
|
@@ -139,7 +141,7 @@ function ModalContent({
|
|
|
139
141
|
if (!isDesktop) {
|
|
140
142
|
return (
|
|
141
143
|
<ModalPortal>
|
|
142
|
-
<ModalOverlay animated={animated} transition={transition} />
|
|
144
|
+
<ModalOverlay animated={animated} transition={transition} className={overlayClassName} />
|
|
143
145
|
<VaulDrawer.Content
|
|
144
146
|
className={cn(
|
|
145
147
|
'fixed bottom-0 left-0 right-0 z-50 flex flex-col bg-background-neutral-base rounded-t-16 max-h-[85vh] shadow-tooltip',
|
|
@@ -163,7 +165,7 @@ function ModalContent({
|
|
|
163
165
|
|
|
164
166
|
return (
|
|
165
167
|
<ModalPortal>
|
|
166
|
-
<ModalOverlay animated={animated} transition={transition} />
|
|
168
|
+
<ModalOverlay animated={animated} transition={transition} className={overlayClassName} />
|
|
167
169
|
<DialogPrimitive.Content className={baseClasses} {...props}>
|
|
168
170
|
<div className="relative size-full">
|
|
169
171
|
<div className="pointer-events-none absolute inset-0 shadow-separator-inset rounded-16" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './shiny-text';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {cn} from 'utils/cn';
|
|
2
|
+
|
|
3
|
+
type ShinyTextProps = {
|
|
4
|
+
text: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
speed?: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function ShinyText({text, disabled = false, speed = 5, className = ''}: ShinyTextProps) {
|
|
11
|
+
const animationDuration = `${speed}s`;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<span className={cn('shiny-text', {disabled: disabled}, className)} style={{animationDuration}}>
|
|
15
|
+
{text}
|
|
16
|
+
</span>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {ShinyText};
|
|
21
|
+
export type {ShinyTextProps};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './tabs';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {useState} from 'react';
|
|
3
|
+
import {Tabs, TabsContent, TabsContents, TabsList, TabsTrigger} from '.';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Tabs',
|
|
7
|
+
component: Tabs,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
},
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
} satisfies Meta<typeof Tabs>;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {defaultValue: 'analytics'} as never,
|
|
19
|
+
render: () => (
|
|
20
|
+
<div className="bg-background-neutral-background p-24 w-[80vw]">
|
|
21
|
+
<Tabs defaultValue="analytics">
|
|
22
|
+
<TabsList className="gap-12 border-b border-neutral-strong">
|
|
23
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
24
|
+
<TabsTrigger value="jobs">Jobs</TabsTrigger>
|
|
25
|
+
</TabsList>
|
|
26
|
+
</Tabs>
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Controlled: Story = {
|
|
32
|
+
args: {value: 'analytics', onValueChange: () => undefined} as never,
|
|
33
|
+
render: () => {
|
|
34
|
+
const [value, setValue] = useState('analytics');
|
|
35
|
+
return (
|
|
36
|
+
<div className="bg-background-neutral-background p-24 w-[80vw]">
|
|
37
|
+
<Tabs value={value} onValueChange={setValue}>
|
|
38
|
+
<TabsList className="gap-12 border-b border-neutral-strong">
|
|
39
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
40
|
+
<TabsTrigger value="jobs">Jobs</TabsTrigger>
|
|
41
|
+
</TabsList>
|
|
42
|
+
<TabsContents>
|
|
43
|
+
<TabsContent value="analytics">
|
|
44
|
+
<div className="py-16">
|
|
45
|
+
<p className="text-foreground-neutral-base">
|
|
46
|
+
Analytics content - Current value: {value}
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
</TabsContent>
|
|
50
|
+
<TabsContent value="jobs">
|
|
51
|
+
<div className="py-16">
|
|
52
|
+
<p className="text-foreground-neutral-base">
|
|
53
|
+
Jobs content - Current value: {value}
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
</TabsContent>
|
|
57
|
+
</TabsContents>
|
|
58
|
+
</Tabs>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const MultipleTabs: Story = {
|
|
65
|
+
args: {defaultValue: 'tab1'} as never,
|
|
66
|
+
render: () => (
|
|
67
|
+
<div className="bg-background-neutral-background p-24 w-[80vw]">
|
|
68
|
+
<Tabs defaultValue="tab1">
|
|
69
|
+
<TabsList className="gap-12 border-b border-neutral-strong">
|
|
70
|
+
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
71
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
72
|
+
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
|
|
73
|
+
<TabsTrigger value="tab4">Tab 4</TabsTrigger>
|
|
74
|
+
</TabsList>
|
|
75
|
+
<TabsContents>
|
|
76
|
+
<TabsContent value="tab1">
|
|
77
|
+
<div className="py-16">
|
|
78
|
+
<p className="text-foreground-neutral-base">Content for Tab 1</p>
|
|
79
|
+
</div>
|
|
80
|
+
</TabsContent>
|
|
81
|
+
<TabsContent value="tab2">
|
|
82
|
+
<div className="py-16">
|
|
83
|
+
<p className="text-foreground-neutral-base">Content for Tab 2</p>
|
|
84
|
+
</div>
|
|
85
|
+
</TabsContent>
|
|
86
|
+
<TabsContent value="tab3">
|
|
87
|
+
<div className="py-16">
|
|
88
|
+
<p className="text-foreground-neutral-base">Content for Tab 3</p>
|
|
89
|
+
</div>
|
|
90
|
+
</TabsContent>
|
|
91
|
+
<TabsContent value="tab4">
|
|
92
|
+
<div className="py-16">
|
|
93
|
+
<p className="text-foreground-neutral-base">Content for Tab 4</p>
|
|
94
|
+
</div>
|
|
95
|
+
</TabsContent>
|
|
96
|
+
</TabsContents>
|
|
97
|
+
</Tabs>
|
|
98
|
+
</div>
|
|
99
|
+
),
|
|
100
|
+
};
|