@shipfox/react-ui 0.13.0 → 0.15.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 (268) hide show
  1. package/.storybook/preview.tsx +7 -0
  2. package/.turbo/turbo-build.log +7 -7
  3. package/.turbo/turbo-check.log +2 -2
  4. package/.turbo/turbo-type.log +1 -1
  5. package/CHANGELOG.md +16 -0
  6. package/dist/components/avatar/avatar.js +1 -1
  7. package/dist/components/avatar/avatar.js.map +1 -1
  8. package/dist/components/button-group/button-group.d.ts +17 -0
  9. package/dist/components/button-group/button-group.d.ts.map +1 -0
  10. package/dist/components/button-group/button-group.js +74 -0
  11. package/dist/components/button-group/button-group.js.map +1 -0
  12. package/dist/components/button-group/button-group.stories.js +644 -0
  13. package/dist/components/button-group/button-group.stories.js.map +1 -0
  14. package/dist/components/button-group/index.d.ts +2 -0
  15. package/dist/components/button-group/index.d.ts.map +1 -0
  16. package/dist/components/button-group/index.js +3 -0
  17. package/dist/components/button-group/index.js.map +1 -0
  18. package/dist/components/code-block/code-block-footer.d.ts.map +1 -1
  19. package/dist/components/code-block/code-block-footer.js +13 -5
  20. package/dist/components/code-block/code-block-footer.js.map +1 -1
  21. package/dist/components/command/command.d.ts +28 -0
  22. package/dist/components/command/command.d.ts.map +1 -0
  23. package/dist/components/command/command.js +190 -0
  24. package/dist/components/command/command.js.map +1 -0
  25. package/dist/components/command/command.stories.js +228 -0
  26. package/dist/components/command/command.stories.js.map +1 -0
  27. package/dist/components/command/index.d.ts +2 -0
  28. package/dist/components/command/index.d.ts.map +1 -0
  29. package/dist/components/command/index.js +3 -0
  30. package/dist/components/command/index.js.map +1 -0
  31. package/dist/components/confetti/confetti.d.ts +21 -0
  32. package/dist/components/confetti/confetti.d.ts.map +1 -0
  33. package/dist/components/confetti/confetti.js +101 -0
  34. package/dist/components/confetti/confetti.js.map +1 -0
  35. package/dist/components/confetti/confetti.stories.js +41 -0
  36. package/dist/components/confetti/confetti.stories.js.map +1 -0
  37. package/dist/components/confetti/index.d.ts +2 -0
  38. package/dist/components/confetti/index.d.ts.map +1 -0
  39. package/dist/components/confetti/index.js +3 -0
  40. package/dist/components/confetti/index.js.map +1 -0
  41. package/dist/components/dashboard/components/analytics-content.d.ts +2 -0
  42. package/dist/components/dashboard/components/analytics-content.d.ts.map +1 -0
  43. package/dist/components/dashboard/components/analytics-content.js +180 -0
  44. package/dist/components/dashboard/components/analytics-content.js.map +1 -0
  45. package/dist/components/dashboard/components/animated-logo.d.ts +4 -0
  46. package/dist/components/dashboard/components/animated-logo.d.ts.map +1 -0
  47. package/dist/components/dashboard/components/animated-logo.js +23 -0
  48. package/dist/components/dashboard/components/animated-logo.js.map +1 -0
  49. package/dist/components/dashboard/components/complete-setup-button.d.ts +4 -0
  50. package/dist/components/dashboard/components/complete-setup-button.d.ts.map +1 -0
  51. package/dist/components/dashboard/components/complete-setup-button.js +28 -0
  52. package/dist/components/dashboard/components/complete-setup-button.js.map +1 -0
  53. package/dist/components/dashboard/components/jobs-content.d.ts +2 -0
  54. package/dist/components/dashboard/components/jobs-content.d.ts.map +1 -0
  55. package/dist/components/dashboard/components/jobs-content.js +69 -0
  56. package/dist/components/dashboard/components/jobs-content.js.map +1 -0
  57. package/dist/components/dashboard/components/mobile-menu.d.ts +2 -0
  58. package/dist/components/dashboard/components/mobile-menu.d.ts.map +1 -0
  59. package/dist/components/dashboard/components/mobile-menu.js +65 -0
  60. package/dist/components/dashboard/components/mobile-menu.js.map +1 -0
  61. package/dist/components/dashboard/components/organization-selector.d.ts +2 -0
  62. package/dist/components/dashboard/components/organization-selector.d.ts.map +1 -0
  63. package/dist/components/dashboard/components/organization-selector.js +92 -0
  64. package/dist/components/dashboard/components/organization-selector.js.map +1 -0
  65. package/dist/components/dashboard/components/top-menu.d.ts +5 -0
  66. package/dist/components/dashboard/components/top-menu.d.ts.map +1 -0
  67. package/dist/components/dashboard/components/top-menu.js +31 -0
  68. package/dist/components/dashboard/components/top-menu.js.map +1 -0
  69. package/dist/components/dashboard/components/topbar-button.d.ts +7 -0
  70. package/dist/components/dashboard/components/topbar-button.d.ts.map +1 -0
  71. package/dist/components/dashboard/components/topbar-button.js +18 -0
  72. package/dist/components/dashboard/components/topbar-button.js.map +1 -0
  73. package/dist/components/dashboard/components/topbar.d.ts +4 -0
  74. package/dist/components/dashboard/components/topbar.d.ts.map +1 -0
  75. package/dist/components/dashboard/components/topbar.js +62 -0
  76. package/dist/components/dashboard/components/topbar.js.map +1 -0
  77. package/dist/components/dashboard/components/user-profile.d.ts +2 -0
  78. package/dist/components/dashboard/components/user-profile.d.ts.map +1 -0
  79. package/dist/components/dashboard/components/user-profile.js +146 -0
  80. package/dist/components/dashboard/components/user-profile.js.map +1 -0
  81. package/dist/components/dashboard/dashboard.d.ts +2 -0
  82. package/dist/components/dashboard/dashboard.d.ts.map +1 -0
  83. package/dist/components/dashboard/dashboard.js +70 -0
  84. package/dist/components/dashboard/dashboard.js.map +1 -0
  85. package/dist/components/dashboard/dashboard.stories.js +23 -0
  86. package/dist/components/dashboard/dashboard.stories.js.map +1 -0
  87. package/dist/components/dashboard/index.d.ts +2 -0
  88. package/dist/components/dashboard/index.d.ts.map +1 -0
  89. package/dist/components/dashboard/index.js +3 -0
  90. package/dist/components/dashboard/index.js.map +1 -0
  91. package/dist/components/form/form.stories.js +6 -1
  92. package/dist/components/form/form.stories.js.map +1 -1
  93. package/dist/components/icon/icon.d.ts +3 -2
  94. package/dist/components/icon/icon.d.ts.map +1 -1
  95. package/dist/components/icon/icon.js +7 -2
  96. package/dist/components/icon/icon.js.map +1 -1
  97. package/dist/components/index.d.ts +9 -0
  98. package/dist/components/index.d.ts.map +1 -1
  99. package/dist/components/index.js +9 -0
  100. package/dist/components/index.js.map +1 -1
  101. package/dist/components/kbd/index.d.ts +2 -0
  102. package/dist/components/kbd/index.d.ts.map +1 -0
  103. package/dist/components/kbd/index.js +3 -0
  104. package/dist/components/kbd/index.js.map +1 -0
  105. package/dist/components/kbd/kbd.d.ts +7 -0
  106. package/dist/components/kbd/kbd.d.ts.map +1 -0
  107. package/dist/components/kbd/kbd.js +18 -0
  108. package/dist/components/kbd/kbd.js.map +1 -0
  109. package/dist/components/kbd/kbd.stories.js +119 -0
  110. package/dist/components/kbd/kbd.stories.js.map +1 -0
  111. package/dist/components/modal/modal.stories.js +227 -168
  112. package/dist/components/modal/modal.stories.js.map +1 -1
  113. package/dist/components/search/index.d.ts +7 -0
  114. package/dist/components/search/index.d.ts.map +1 -0
  115. package/dist/components/search/index.js +8 -0
  116. package/dist/components/search/index.js.map +1 -0
  117. package/dist/components/search/search-context.d.ts +11 -0
  118. package/dist/components/search/search-context.d.ts.map +1 -0
  119. package/dist/components/search/search-context.js +56 -0
  120. package/dist/components/search/search-context.js.map +1 -0
  121. package/dist/components/search/search-inline.d.ts +9 -0
  122. package/dist/components/search/search-inline.d.ts.map +1 -0
  123. package/dist/components/search/search-inline.js +85 -0
  124. package/dist/components/search/search-inline.js.map +1 -0
  125. package/dist/components/search/search-modal.d.ts +25 -0
  126. package/dist/components/search/search-modal.d.ts.map +1 -0
  127. package/dist/components/search/search-modal.js +162 -0
  128. package/dist/components/search/search-modal.js.map +1 -0
  129. package/dist/components/search/search-trigger.d.ts +9 -0
  130. package/dist/components/search/search-trigger.d.ts.map +1 -0
  131. package/dist/components/search/search-trigger.js +37 -0
  132. package/dist/components/search/search-trigger.js.map +1 -0
  133. package/dist/components/search/search-variants.d.ts +14 -0
  134. package/dist/components/search/search-variants.d.ts.map +1 -0
  135. package/dist/components/search/search-variants.js +90 -0
  136. package/dist/components/search/search-variants.js.map +1 -0
  137. package/dist/components/search/search.d.ts +11 -0
  138. package/dist/components/search/search.d.ts.map +1 -0
  139. package/dist/components/search/search.js +35 -0
  140. package/dist/components/search/search.js.map +1 -0
  141. package/dist/components/search/search.stories.js +630 -0
  142. package/dist/components/search/search.stories.js.map +1 -0
  143. package/dist/components/select/index.d.ts +2 -0
  144. package/dist/components/select/index.d.ts.map +1 -0
  145. package/dist/components/select/index.js +3 -0
  146. package/dist/components/select/index.js.map +1 -0
  147. package/dist/components/select/select.d.ts +25 -0
  148. package/dist/components/select/select.d.ts.map +1 -0
  149. package/dist/components/select/select.js +153 -0
  150. package/dist/components/select/select.js.map +1 -0
  151. package/dist/components/select/select.stories.js +393 -0
  152. package/dist/components/select/select.stories.js.map +1 -0
  153. package/dist/components/shiny-text/index.d.ts +2 -0
  154. package/dist/components/shiny-text/index.d.ts.map +1 -0
  155. package/dist/components/shiny-text/index.js +3 -0
  156. package/dist/components/shiny-text/index.js.map +1 -0
  157. package/dist/components/shiny-text/shiny-text.d.ts +10 -0
  158. package/dist/components/shiny-text/shiny-text.d.ts.map +1 -0
  159. package/dist/components/shiny-text/shiny-text.js +17 -0
  160. package/dist/components/shiny-text/shiny-text.js.map +1 -0
  161. package/dist/components/skeleton/index.d.ts +2 -0
  162. package/dist/components/skeleton/index.d.ts.map +1 -0
  163. package/dist/components/skeleton/index.js +3 -0
  164. package/dist/components/skeleton/index.js.map +1 -0
  165. package/dist/components/skeleton/skeleton.d.ts +5 -0
  166. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  167. package/dist/components/skeleton/skeleton.js +11 -0
  168. package/dist/components/skeleton/skeleton.js.map +1 -0
  169. package/dist/components/skeleton/skeleton.stories.js +345 -0
  170. package/dist/components/skeleton/skeleton.stories.js.map +1 -0
  171. package/dist/components/table/data-table.d.ts +70 -0
  172. package/dist/components/table/data-table.d.ts.map +1 -0
  173. package/dist/components/table/data-table.js +159 -0
  174. package/dist/components/table/data-table.js.map +1 -0
  175. package/dist/components/table/index.d.ts +6 -0
  176. package/dist/components/table/index.d.ts.map +1 -0
  177. package/dist/components/table/index.js +6 -0
  178. package/dist/components/table/index.js.map +1 -0
  179. package/dist/components/table/table-column-header.d.ts +79 -0
  180. package/dist/components/table/table-column-header.d.ts.map +1 -0
  181. package/dist/components/table/table-column-header.js +99 -0
  182. package/dist/components/table/table-column-header.js.map +1 -0
  183. package/dist/components/table/table-pagination.d.ts +53 -0
  184. package/dist/components/table/table-pagination.d.ts.map +1 -0
  185. package/dist/components/table/table-pagination.js +139 -0
  186. package/dist/components/table/table-pagination.js.map +1 -0
  187. package/dist/components/table/table.d.ts +11 -0
  188. package/dist/components/table/table.d.ts.map +1 -0
  189. package/dist/components/table/table.js +64 -0
  190. package/dist/components/table/table.js.map +1 -0
  191. package/dist/components/table/table.stories.columns.d.ts +24 -0
  192. package/dist/components/table/table.stories.columns.d.ts.map +1 -0
  193. package/dist/components/table/table.stories.columns.js +310 -0
  194. package/dist/components/table/table.stories.columns.js.map +1 -0
  195. package/dist/components/table/table.stories.components.d.ts +14 -0
  196. package/dist/components/table/table.stories.components.d.ts.map +1 -0
  197. package/dist/components/table/table.stories.components.js +107 -0
  198. package/dist/components/table/table.stories.components.js.map +1 -0
  199. package/dist/components/table/table.stories.data.d.ts +54 -0
  200. package/dist/components/table/table.stories.data.d.ts.map +1 -0
  201. package/dist/components/table/table.stories.data.js +122 -0
  202. package/dist/components/table/table.stories.data.js.map +1 -0
  203. package/dist/components/table/table.stories.js +302 -0
  204. package/dist/components/table/table.stories.js.map +1 -0
  205. package/dist/index.d.ts +1 -0
  206. package/dist/index.d.ts.map +1 -1
  207. package/dist/index.js +1 -0
  208. package/dist/index.js.map +1 -1
  209. package/dist/styles.css +1 -1
  210. package/index.css +79 -0
  211. package/package.json +6 -2
  212. package/src/components/avatar/avatar.tsx +1 -1
  213. package/src/components/button-group/button-group.stories.tsx +361 -0
  214. package/src/components/button-group/button-group.tsx +111 -0
  215. package/src/components/button-group/index.ts +1 -0
  216. package/src/components/code-block/code-block-footer.tsx +19 -2
  217. package/src/components/command/command.stories.tsx +133 -0
  218. package/src/components/command/command.tsx +265 -0
  219. package/src/components/command/index.ts +1 -0
  220. package/src/components/confetti/confetti.stories.tsx +38 -0
  221. package/src/components/confetti/confetti.tsx +140 -0
  222. package/src/components/confetti/index.ts +1 -0
  223. package/src/components/dashboard/components/analytics-content.tsx +102 -0
  224. package/src/components/dashboard/components/animated-logo.tsx +25 -0
  225. package/src/components/dashboard/components/complete-setup-button.tsx +30 -0
  226. package/src/components/dashboard/components/jobs-content.tsx +51 -0
  227. package/src/components/dashboard/components/mobile-menu.tsx +50 -0
  228. package/src/components/dashboard/components/organization-selector.tsx +51 -0
  229. package/src/components/dashboard/components/top-menu.tsx +26 -0
  230. package/src/components/dashboard/components/topbar-button.tsx +27 -0
  231. package/src/components/dashboard/components/topbar.tsx +40 -0
  232. package/src/components/dashboard/components/user-profile.tsx +90 -0
  233. package/src/components/dashboard/dashboard.stories.tsx +25 -0
  234. package/src/components/dashboard/dashboard.tsx +61 -0
  235. package/src/components/dashboard/index.ts +1 -0
  236. package/src/components/form/form.stories.tsx +5 -0
  237. package/src/components/icon/icon.tsx +7 -3
  238. package/src/components/index.ts +9 -0
  239. package/src/components/kbd/index.ts +1 -0
  240. package/src/components/kbd/kbd.stories.tsx +64 -0
  241. package/src/components/kbd/kbd.tsx +32 -0
  242. package/src/components/modal/modal.stories.tsx +58 -4
  243. package/src/components/search/index.ts +28 -0
  244. package/src/components/search/search-context.tsx +78 -0
  245. package/src/components/search/search-inline.tsx +107 -0
  246. package/src/components/search/search-modal.tsx +198 -0
  247. package/src/components/search/search-trigger.tsx +47 -0
  248. package/src/components/search/search-variants.ts +88 -0
  249. package/src/components/search/search.stories.tsx +392 -0
  250. package/src/components/search/search.tsx +47 -0
  251. package/src/components/select/index.ts +1 -0
  252. package/src/components/select/select.stories.tsx +207 -0
  253. package/src/components/select/select.tsx +220 -0
  254. package/src/components/shiny-text/index.ts +1 -0
  255. package/src/components/shiny-text/shiny-text.tsx +21 -0
  256. package/src/components/skeleton/index.ts +1 -0
  257. package/src/components/skeleton/skeleton.stories.tsx +178 -0
  258. package/src/components/skeleton/skeleton.tsx +14 -0
  259. package/src/components/table/data-table.tsx +254 -0
  260. package/src/components/table/index.ts +5 -0
  261. package/src/components/table/table-column-header.tsx +141 -0
  262. package/src/components/table/table-pagination.tsx +161 -0
  263. package/src/components/table/table.stories.columns.tsx +198 -0
  264. package/src/components/table/table.stories.components.tsx +104 -0
  265. package/src/components/table/table.stories.data.ts +117 -0
  266. package/src/components/table/table.stories.tsx +256 -0
  267. package/src/components/table/table.tsx +95 -0
  268. package/src/index.ts +1 -0
@@ -0,0 +1,265 @@
1
+ import {cva, type VariantProps} from 'class-variance-authority';
2
+ import {Command as CommandPrimitive} from 'cmdk';
3
+ import {type ComponentProps, forwardRef, useCallback, useState} from 'react';
4
+ import {cn} from 'utils/cn';
5
+ import {Icon} from '../icon';
6
+ import {Kbd} from '../kbd';
7
+
8
+ const commandTriggerVariants = cva(
9
+ [
10
+ 'flex items-center justify-between gap-8',
11
+ 'w-full rounded-6 px-8 text-sm leading-20',
12
+ 'bg-background-field-base text-foreground-neutral-base',
13
+ 'shadow-button-neutral transition-[color,box-shadow] outline-none',
14
+ 'hover:bg-background-field-hover cursor-pointer',
15
+ 'focus-visible:shadow-border-interactive-with-active',
16
+ 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-background-neutral-disabled disabled:shadow-none disabled:text-foreground-neutral-disabled',
17
+ ],
18
+ {
19
+ variants: {
20
+ variant: {
21
+ base: 'bg-background-field-base',
22
+ component: 'bg-background-field-component',
23
+ },
24
+ size: {
25
+ small: 'h-28 py-4',
26
+ base: 'h-32 py-6',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'base',
31
+ size: 'base',
32
+ },
33
+ },
34
+ );
35
+
36
+ type CommandTriggerProps = ComponentProps<'button'> &
37
+ VariantProps<typeof commandTriggerVariants> & {
38
+ placeholder?: string;
39
+ };
40
+
41
+ const CommandTrigger = forwardRef<HTMLButtonElement, CommandTriggerProps>(
42
+ ({className, variant, size, placeholder, children, ...props}, ref) => {
43
+ const hasValue = Boolean(children);
44
+
45
+ return (
46
+ <button
47
+ ref={ref}
48
+ type="button"
49
+ data-slot="command-trigger"
50
+ data-placeholder={!hasValue || undefined}
51
+ className={cn(
52
+ commandTriggerVariants({variant, size}),
53
+ 'data-placeholder:text-foreground-neutral-muted',
54
+ className,
55
+ )}
56
+ {...props}
57
+ >
58
+ <span className="flex-1 text-left truncate">{hasValue ? children : placeholder}</span>
59
+ <Icon name="arrowDownSLine" className="size-16 text-foreground-neutral-muted shrink-0" />
60
+ </button>
61
+ );
62
+ },
63
+ );
64
+ CommandTrigger.displayName = 'CommandTrigger';
65
+
66
+ function Command({className, ...props}: ComponentProps<typeof CommandPrimitive>) {
67
+ return (
68
+ <CommandPrimitive
69
+ data-slot="command"
70
+ className={cn(
71
+ 'flex h-full w-full flex-col overflow-hidden rounded-10',
72
+ 'bg-background-neutral-overlay text-foreground-neutral-base',
73
+ className,
74
+ )}
75
+ {...props}
76
+ />
77
+ );
78
+ }
79
+
80
+ function CommandDialog({children, ...props}: ComponentProps<typeof CommandPrimitive.Dialog>) {
81
+ return (
82
+ <CommandPrimitive.Dialog data-slot="command-dialog" {...props}>
83
+ <div className="fixed inset-0 z-50 bg-background-neutral-overlay/80 backdrop-blur-sm" />
84
+ <div className="fixed left-1/2 top-1/2 z-50 w-full max-w-600 -translate-x-1/2 -translate-y-1/2 p-16">
85
+ <Command className="shadow-tooltip">{children}</Command>
86
+ </div>
87
+ </CommandPrimitive.Dialog>
88
+ );
89
+ }
90
+
91
+ type CommandInputProps = ComponentProps<typeof CommandPrimitive.Input> & {
92
+ showClearButton?: boolean;
93
+ onClear?: () => void;
94
+ };
95
+
96
+ function CommandInput({
97
+ className,
98
+ value,
99
+ onValueChange,
100
+ onClear,
101
+ showClearButton = true,
102
+ ...props
103
+ }: CommandInputProps) {
104
+ const [internalValue, setInternalValue] = useState('');
105
+ const isControlled = value !== undefined;
106
+ const inputValue = isControlled ? value : internalValue;
107
+ const hasValue = Boolean(inputValue);
108
+
109
+ const handleValueChange = useCallback(
110
+ (newValue: string) => {
111
+ if (!isControlled) {
112
+ setInternalValue(newValue);
113
+ }
114
+ onValueChange?.(newValue);
115
+ },
116
+ [isControlled, onValueChange],
117
+ );
118
+
119
+ const handleClear = useCallback(() => {
120
+ if (!isControlled) {
121
+ setInternalValue('');
122
+ }
123
+ onValueChange?.('');
124
+ onClear?.();
125
+ }, [isControlled, onValueChange, onClear]);
126
+
127
+ const handleKeyDown = useCallback(
128
+ (e: React.KeyboardEvent<HTMLInputElement>) => {
129
+ if (e.key === 'Escape' && hasValue) {
130
+ e.preventDefault();
131
+ e.stopPropagation();
132
+ handleClear();
133
+ }
134
+ },
135
+ [hasValue, handleClear],
136
+ );
137
+
138
+ return (
139
+ <div className="flex items-center gap-8 border-b border-border-neutral-strong p-8">
140
+ <Icon name="searchLine" className="size-16 shrink-0 text-foreground-neutral-muted" />
141
+ <CommandPrimitive.Input
142
+ data-slot="command-input"
143
+ value={inputValue}
144
+ onValueChange={handleValueChange}
145
+ onKeyDown={handleKeyDown}
146
+ className={cn(
147
+ 'flex-1 bg-transparent text-sm leading-20 outline-none',
148
+ 'placeholder:text-foreground-neutral-muted',
149
+ 'disabled:cursor-not-allowed disabled:text-foreground-neutral-disabled',
150
+ className,
151
+ )}
152
+ {...props}
153
+ />
154
+ {showClearButton && hasValue && (
155
+ <button
156
+ type="button"
157
+ onClick={handleClear}
158
+ className={cn(
159
+ 'shrink-0 cursor-pointer rounded-4 p-2',
160
+ 'text-foreground-neutral-muted hover:text-foreground-neutral-subtle transition-colors',
161
+ )}
162
+ aria-label="Clear search"
163
+ >
164
+ <Icon name="closeLine" className="size-16" />
165
+ </button>
166
+ )}
167
+ </div>
168
+ );
169
+ }
170
+
171
+ function CommandList({className, ...props}: ComponentProps<typeof CommandPrimitive.List>) {
172
+ return (
173
+ <CommandPrimitive.List
174
+ data-slot="command-list"
175
+ className={cn('max-h-300 overflow-y-auto overflow-x-hidden p-4 scrollbar', className)}
176
+ {...props}
177
+ />
178
+ );
179
+ }
180
+
181
+ function CommandEmpty({className, ...props}: ComponentProps<typeof CommandPrimitive.Empty>) {
182
+ return (
183
+ <CommandPrimitive.Empty
184
+ data-slot="command-empty"
185
+ className={cn('py-24 text-center text-sm text-foreground-neutral-muted', className)}
186
+ {...props}
187
+ />
188
+ );
189
+ }
190
+
191
+ function CommandGroup({className, ...props}: ComponentProps<typeof CommandPrimitive.Group>) {
192
+ return (
193
+ <CommandPrimitive.Group
194
+ data-slot="command-group"
195
+ className={cn(
196
+ 'overflow-hidden',
197
+ '[&_[cmdk-group-heading]]:px-8 [&_[cmdk-group-heading]]:py-4',
198
+ '[&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:leading-20',
199
+ '[&_[cmdk-group-heading]]:text-foreground-neutral-subtle',
200
+ '[&_[cmdk-group-heading]]:select-none',
201
+ className,
202
+ )}
203
+ {...props}
204
+ />
205
+ );
206
+ }
207
+
208
+ function CommandSeparator({
209
+ className,
210
+ ...props
211
+ }: ComponentProps<typeof CommandPrimitive.Separator>) {
212
+ return (
213
+ <CommandPrimitive.Separator
214
+ data-slot="command-separator"
215
+ className={cn(
216
+ 'relative -mx-4 my-4 h-px',
217
+ 'bg-border-neutral-menu-top',
218
+ 'after:absolute after:inset-x-0 after:top-px after:h-px',
219
+ 'after:bg-border-neutral-menu-bottom',
220
+ className,
221
+ )}
222
+ {...props}
223
+ />
224
+ );
225
+ }
226
+
227
+ function CommandItem({className, ...props}: ComponentProps<typeof CommandPrimitive.Item>) {
228
+ return (
229
+ <CommandPrimitive.Item
230
+ data-slot="command-item"
231
+ className={cn(
232
+ 'relative flex cursor-pointer select-none items-center gap-8 rounded-6 px-8 py-6',
233
+ 'text-sm leading-20 text-foreground-neutral-subtle outline-none transition-colors',
234
+ 'aria-selected:bg-background-components-hover aria-selected:text-foreground-neutral-base',
235
+ 'data-[disabled=true]:pointer-events-none data-[disabled=true]:text-foreground-neutral-disabled',
236
+ className,
237
+ )}
238
+ {...props}
239
+ />
240
+ );
241
+ }
242
+
243
+ function CommandShortcut({className, children, ...props}: ComponentProps<typeof Kbd>) {
244
+ return (
245
+ <Kbd data-slot="command-shortcut" className={cn('ml-auto', className)} {...props}>
246
+ {children}
247
+ </Kbd>
248
+ );
249
+ }
250
+
251
+ export {
252
+ Command,
253
+ CommandTrigger,
254
+ CommandDialog,
255
+ CommandInput,
256
+ CommandList,
257
+ CommandEmpty,
258
+ CommandGroup,
259
+ CommandItem,
260
+ CommandSeparator,
261
+ CommandShortcut,
262
+ commandTriggerVariants,
263
+ };
264
+
265
+ export type {CommandTriggerProps, CommandInputProps};
@@ -0,0 +1 @@
1
+ export * from './command';
@@ -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';
@@ -0,0 +1,102 @@
1
+ import {Icon} from 'components/icon';
2
+ import {Skeleton} from 'components/skeleton';
3
+ import {Text} from 'components/typography';
4
+
5
+ export function AnalyticsContent() {
6
+ return (
7
+ <div className="min-h-[calc(100vh-48px)] p-12 md:p-24 space-y-16 md:space-y-20 bg-background-neutral-base">
8
+ <div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-12 md:gap-0">
9
+ <Skeleton className="h-28 md:h-32 w-120 md:w-160" />
10
+ <div className="flex items-center gap-8 md:gap-16">
11
+ <Skeleton className="h-28 md:h-32 w-80 md:w-100" />
12
+ <Skeleton className="h-28 md:h-32 w-100 md:w-160" />
13
+ </div>
14
+ </div>
15
+
16
+ <div className="flex gap-12 md:gap-16 overflow-x-auto scrollbar pb-4 md:pb-0 -mx-12 px-12 md:mx-0 md:px-0">
17
+ {['Total', 'Success', 'Failed', 'Neutral', 'Failure rate'].map((label) => (
18
+ <div
19
+ key={label}
20
+ className="shrink-0 w-100 md:w-auto md:flex-1 p-12 rounded-8 bg-background-neutral-base border border-border-neutral-base"
21
+ >
22
+ <p className="text-xs text-foreground-neutral-subtle mb-4">{label}</p>
23
+ <Skeleton className="h-20 w-40" />
24
+ </div>
25
+ ))}
26
+ </div>
27
+
28
+ <div className="flex flex-col md:flex-row gap-16 md:gap-20">
29
+ <div className="flex-1 p-12 rounded-8 bg-background-neutral-base border border-border-neutral-base">
30
+ <p className="text-sm font-medium text-foreground-neutral-base mb-12">
31
+ Performance over time
32
+ </p>
33
+ <div className="h-120 md:h-160 flex items-center justify-center">
34
+ <div className="text-center">
35
+ <Icon
36
+ name="fileChartLine"
37
+ className="size-24 text-foreground-neutral-muted mx-auto mb-8"
38
+ />
39
+ <p className="text-sm text-foreground-neutral-subtle">Nothing here yet.</p>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ <div className="flex-1 p-12 rounded-8 bg-background-neutral-base border border-border-neutral-base">
44
+ <p className="text-sm font-medium text-foreground-neutral-base mb-12">
45
+ Duration distribution
46
+ </p>
47
+ <div className="h-120 md:h-160 flex items-center justify-center">
48
+ <div className="text-center">
49
+ <Icon
50
+ name="barChartBoxLine"
51
+ className="size-24 text-foreground-neutral-muted mx-auto mb-8"
52
+ />
53
+ <p className="text-sm text-foreground-neutral-subtle">Nothing here yet.</p>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <div className="rounded-8 bg-background-neutral-base border border-border-neutral-base overflow-hidden">
60
+ <div className="flex flex-col md:flex-row items-start md:items-center justify-between p-12 gap-12 md:gap-0 border-b border-border-neutral-strong">
61
+ <p className="text-sm font-medium text-foreground-neutral-base">Jobs breakdown</p>
62
+ <div className="flex items-center gap-8 md:gap-16 w-full md:w-auto">
63
+ <Skeleton className="h-28 flex-1 md:flex-none md:w-200" />
64
+ <Skeleton className="h-28 w-28 shrink-0" />
65
+ </div>
66
+ </div>
67
+ <div className="py-48 md:py-64 flex flex-col items-center justify-center gap-12">
68
+ <div className="size-32 rounded-6 bg-transparent border border-border-neutral-strong flex items-center justify-center">
69
+ <Icon
70
+ name="shipfox"
71
+ className="size-16 text-foreground-neutral-subtle"
72
+ color="var(--foreground-neutral-subtle, #a1a1aa)"
73
+ />
74
+ </div>
75
+ <div className="text-center space-y-4 px-16">
76
+ <Text size="sm" className="text-foreground-neutral-base">
77
+ No jobs yet
78
+ </Text>
79
+ <Text size="xs" className="text-foreground-neutral-muted">
80
+ Import past runs or start a runner.
81
+ </Text>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <div className="space-y-16">
87
+ {Array.from({length: 3}).map((_, i) => {
88
+ const blockId = `analytics-extra-block-${i}`;
89
+ return (
90
+ <div
91
+ key={blockId}
92
+ className="p-12 md:p-16 rounded-8 bg-background-subtle-base border border-border-neutral-strong"
93
+ >
94
+ <Skeleton className="h-16 w-full max-w-400 mb-8" />
95
+ <Skeleton className="h-12 w-full max-w-600" />
96
+ </div>
97
+ );
98
+ })}
99
+ </div>
100
+ </div>
101
+ );
102
+ }
@@ -0,0 +1,25 @@
1
+ import {Icon} from 'components/icon';
2
+ import {motion} from 'framer-motion';
3
+
4
+ const LOGO_HEIGHT = 48;
5
+
6
+ export function AnimatedLogo({scrollProgress}: {scrollProgress: number}) {
7
+ const isVisible = scrollProgress > 0;
8
+
9
+ if (!isVisible) return null;
10
+
11
+ const easedProgress = 1 - (1 - scrollProgress) ** 3;
12
+
13
+ return (
14
+ <motion.div
15
+ className="fixed top-0 left-0 z-50 flex items-center justify-center shrink-0 w-48 h-48 bg-background-neutral-base"
16
+ style={{
17
+ opacity: easedProgress,
18
+ transform: `translateY(${-LOGO_HEIGHT + easedProgress * LOGO_HEIGHT}px)`,
19
+ }}
20
+ initial={false}
21
+ >
22
+ <Icon name="shipfox" className="size-20 text-foreground-neutral-subtle" />
23
+ </motion.div>
24
+ );
25
+ }
@@ -0,0 +1,30 @@
1
+ import {Button} from 'components/button';
2
+ import {ShinyText} from 'components/shiny-text';
3
+ import {useResolvedTheme} from 'hooks/useResolvedTheme';
4
+ import {ShipfoxLoader} from 'shipfox-loader-react';
5
+ import {cn} from 'utils/cn';
6
+
7
+ export function CompleteSetupButton({className}: {className?: string}) {
8
+ const resolvedTheme = useResolvedTheme();
9
+ return (
10
+ <Button
11
+ type="button"
12
+ variant="transparent"
13
+ className={cn(
14
+ 'flex items-center gap-8 min-w-124 max-w-280 overflow-hidden px-12 py-10 transition-colors rounded-none h-40 border-l border-border-neutral-strong',
15
+ className,
16
+ )}
17
+ >
18
+ <ShipfoxLoader
19
+ size={13}
20
+ animation="circular"
21
+ color={resolvedTheme === 'dark' ? 'white' : 'orange'}
22
+ background={resolvedTheme === 'dark' ? 'dark' : 'light'}
23
+ />
24
+ <ShinyText
25
+ text="Complete setup"
26
+ className="flex-1 text-sm font-medium leading-20 text-foreground-neutral-base truncate text-left"
27
+ />
28
+ </Button>
29
+ );
30
+ }
@@ -0,0 +1,51 @@
1
+ import {Button} from 'components/button';
2
+ import {Icon} from 'components/icon';
3
+ import {SearchInline} from 'components/search/search-inline';
4
+ import {DataTable} from 'components/table/data-table';
5
+ import {jobColumns} from 'components/table/table.stories.columns';
6
+ import {jobsData} from 'components/table/table.stories.data';
7
+ import {Header as TypographyHeader} from 'components/typography';
8
+ import {useMemo, useState} from 'react';
9
+
10
+ export function JobsContent() {
11
+ const [searchQuery, setSearchQuery] = useState('');
12
+
13
+ const filteredData = useMemo(
14
+ () => jobsData.filter((job) => job.name.toLowerCase().includes(searchQuery.toLowerCase())),
15
+ [searchQuery],
16
+ );
17
+
18
+ return (
19
+ <div className="min-h-[calc(100vh-48px)] p-12 md:p-24 bg-background-neutral-base">
20
+ <div className="rounded-t-8 overflow-hidden">
21
+ <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-12 md:gap-0 p-12 border-t border-x border-border-neutral-base rounded-t-8 bg-background-neutral-base">
22
+ <TypographyHeader variant="h3" className="text-foreground-neutral-base">
23
+ Jobs breakdown
24
+ </TypographyHeader>
25
+
26
+ <div className="flex items-center gap-8 md:gap-16 w-full md:w-auto">
27
+ <SearchInline
28
+ placeholder="Search..."
29
+ value={searchQuery}
30
+ onChange={(e) => setSearchQuery(e.target.value)}
31
+ onClear={() => setSearchQuery('')}
32
+ className="flex-1 md:w-240"
33
+ />
34
+ <Button variant="secondary" aria-label="Insert column left" className="shrink-0">
35
+ <Icon name="insertColumnLeft" className="size-16 text-foreground-neutral-subtle" />
36
+ </Button>
37
+ </div>
38
+ </div>
39
+
40
+ <DataTable
41
+ columns={jobColumns}
42
+ data={filteredData}
43
+ pagination={true}
44
+ pageSize={10}
45
+ pageSizeOptions={[10, 20, 50, 100]}
46
+ className="rounded-t-none"
47
+ />
48
+ </div>
49
+ </div>
50
+ );
51
+ }