@shipfox/react-ui 0.13.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.
Files changed (52) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/.turbo/turbo-check.log +2 -2
  3. package/.turbo/turbo-type.log +1 -1
  4. package/CHANGELOG.md +6 -0
  5. package/dist/components/code-block/code-block-footer.d.ts.map +1 -1
  6. package/dist/components/code-block/code-block-footer.js +11 -5
  7. package/dist/components/code-block/code-block-footer.js.map +1 -1
  8. package/dist/components/confetti/confetti.d.ts +21 -0
  9. package/dist/components/confetti/confetti.d.ts.map +1 -0
  10. package/dist/components/confetti/confetti.js +101 -0
  11. package/dist/components/confetti/confetti.js.map +1 -0
  12. package/dist/components/confetti/confetti.stories.js +41 -0
  13. package/dist/components/confetti/confetti.stories.js.map +1 -0
  14. package/dist/components/confetti/index.d.ts +2 -0
  15. package/dist/components/confetti/index.d.ts.map +1 -0
  16. package/dist/components/confetti/index.js +3 -0
  17. package/dist/components/confetti/index.js.map +1 -0
  18. package/dist/components/icon/icon.d.ts +3 -2
  19. package/dist/components/icon/icon.d.ts.map +1 -1
  20. package/dist/components/icon/icon.js +7 -2
  21. package/dist/components/icon/icon.js.map +1 -1
  22. package/dist/components/index.d.ts +2 -0
  23. package/dist/components/index.d.ts.map +1 -1
  24. package/dist/components/index.js +2 -0
  25. package/dist/components/index.js.map +1 -1
  26. package/dist/components/modal/modal.stories.js +227 -168
  27. package/dist/components/modal/modal.stories.js.map +1 -1
  28. package/dist/components/shiny-text/index.d.ts +2 -0
  29. package/dist/components/shiny-text/index.d.ts.map +1 -0
  30. package/dist/components/shiny-text/index.js +3 -0
  31. package/dist/components/shiny-text/index.js.map +1 -0
  32. package/dist/components/shiny-text/shiny-text.d.ts +10 -0
  33. package/dist/components/shiny-text/shiny-text.d.ts.map +1 -0
  34. package/dist/components/shiny-text/shiny-text.js +17 -0
  35. package/dist/components/shiny-text/shiny-text.js.map +1 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/styles.css +1 -1
  41. package/index.css +31 -0
  42. package/package.json +4 -1
  43. package/src/components/code-block/code-block-footer.tsx +12 -2
  44. package/src/components/confetti/confetti.stories.tsx +38 -0
  45. package/src/components/confetti/confetti.tsx +140 -0
  46. package/src/components/confetti/index.ts +1 -0
  47. package/src/components/icon/icon.tsx +7 -3
  48. package/src/components/index.ts +2 -0
  49. package/src/components/modal/modal.stories.tsx +58 -4
  50. package/src/components/shiny-text/index.ts +1 -0
  51. package/src/components/shiny-text/shiny-text.tsx +21 -0
  52. package/src/index.ts +1 -0
@@ -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';
@@ -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<BaseIconProps, 'name'>;
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
- return <IconComponent {...props} />;
88
+ const svgProps = size && typeof size === 'number' ? {...props, width: size, height: size} : props;
89
+ return <IconComponent {...svgProps} />;
86
90
  }
@@ -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';
@@ -19,6 +20,7 @@ export * from './label';
19
20
  export * from './modal';
20
21
  export * from './moving-border';
21
22
  export * from './popover';
23
+ export * from './shiny-text';
22
24
  export * from './tabs';
23
25
  export * from './textarea';
24
26
  export * from './theme';
@@ -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';
@@ -214,9 +215,54 @@ 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>
@@ -316,9 +362,17 @@ export const GithubActions: Story = {
316
362
  )}
317
363
  </CodeBlockBody>
318
364
  <CodeBlockFooter
319
- state="running"
320
- message="Waiting for Shipfox runner event…"
321
- description="This usually takes 30-60 seconds after you commit the workflow file."
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>
@@ -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};
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export {ShipfoxLoader} from 'shipfox-loader-react';
1
2
  export * from './components';
2
3
  export * from './hooks';
3
4
  export * from './utils';