@shipfox/react-ui 0.14.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 (232) 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 +10 -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 +4 -2
  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/dashboard/components/analytics-content.d.ts +2 -0
  32. package/dist/components/dashboard/components/analytics-content.d.ts.map +1 -0
  33. package/dist/components/dashboard/components/analytics-content.js +180 -0
  34. package/dist/components/dashboard/components/analytics-content.js.map +1 -0
  35. package/dist/components/dashboard/components/animated-logo.d.ts +4 -0
  36. package/dist/components/dashboard/components/animated-logo.d.ts.map +1 -0
  37. package/dist/components/dashboard/components/animated-logo.js +23 -0
  38. package/dist/components/dashboard/components/animated-logo.js.map +1 -0
  39. package/dist/components/dashboard/components/complete-setup-button.d.ts +4 -0
  40. package/dist/components/dashboard/components/complete-setup-button.d.ts.map +1 -0
  41. package/dist/components/dashboard/components/complete-setup-button.js +28 -0
  42. package/dist/components/dashboard/components/complete-setup-button.js.map +1 -0
  43. package/dist/components/dashboard/components/jobs-content.d.ts +2 -0
  44. package/dist/components/dashboard/components/jobs-content.d.ts.map +1 -0
  45. package/dist/components/dashboard/components/jobs-content.js +69 -0
  46. package/dist/components/dashboard/components/jobs-content.js.map +1 -0
  47. package/dist/components/dashboard/components/mobile-menu.d.ts +2 -0
  48. package/dist/components/dashboard/components/mobile-menu.d.ts.map +1 -0
  49. package/dist/components/dashboard/components/mobile-menu.js +65 -0
  50. package/dist/components/dashboard/components/mobile-menu.js.map +1 -0
  51. package/dist/components/dashboard/components/organization-selector.d.ts +2 -0
  52. package/dist/components/dashboard/components/organization-selector.d.ts.map +1 -0
  53. package/dist/components/dashboard/components/organization-selector.js +92 -0
  54. package/dist/components/dashboard/components/organization-selector.js.map +1 -0
  55. package/dist/components/dashboard/components/top-menu.d.ts +5 -0
  56. package/dist/components/dashboard/components/top-menu.d.ts.map +1 -0
  57. package/dist/components/dashboard/components/top-menu.js +31 -0
  58. package/dist/components/dashboard/components/top-menu.js.map +1 -0
  59. package/dist/components/dashboard/components/topbar-button.d.ts +7 -0
  60. package/dist/components/dashboard/components/topbar-button.d.ts.map +1 -0
  61. package/dist/components/dashboard/components/topbar-button.js +18 -0
  62. package/dist/components/dashboard/components/topbar-button.js.map +1 -0
  63. package/dist/components/dashboard/components/topbar.d.ts +4 -0
  64. package/dist/components/dashboard/components/topbar.d.ts.map +1 -0
  65. package/dist/components/dashboard/components/topbar.js +62 -0
  66. package/dist/components/dashboard/components/topbar.js.map +1 -0
  67. package/dist/components/dashboard/components/user-profile.d.ts +2 -0
  68. package/dist/components/dashboard/components/user-profile.d.ts.map +1 -0
  69. package/dist/components/dashboard/components/user-profile.js +146 -0
  70. package/dist/components/dashboard/components/user-profile.js.map +1 -0
  71. package/dist/components/dashboard/dashboard.d.ts +2 -0
  72. package/dist/components/dashboard/dashboard.d.ts.map +1 -0
  73. package/dist/components/dashboard/dashboard.js +70 -0
  74. package/dist/components/dashboard/dashboard.js.map +1 -0
  75. package/dist/components/dashboard/dashboard.stories.js +23 -0
  76. package/dist/components/dashboard/dashboard.stories.js.map +1 -0
  77. package/dist/components/dashboard/index.d.ts +2 -0
  78. package/dist/components/dashboard/index.d.ts.map +1 -0
  79. package/dist/components/dashboard/index.js +3 -0
  80. package/dist/components/dashboard/index.js.map +1 -0
  81. package/dist/components/form/form.stories.js +6 -1
  82. package/dist/components/form/form.stories.js.map +1 -1
  83. package/dist/components/index.d.ts +7 -0
  84. package/dist/components/index.d.ts.map +1 -1
  85. package/dist/components/index.js +7 -0
  86. package/dist/components/index.js.map +1 -1
  87. package/dist/components/kbd/index.d.ts +2 -0
  88. package/dist/components/kbd/index.d.ts.map +1 -0
  89. package/dist/components/kbd/index.js +3 -0
  90. package/dist/components/kbd/index.js.map +1 -0
  91. package/dist/components/kbd/kbd.d.ts +7 -0
  92. package/dist/components/kbd/kbd.d.ts.map +1 -0
  93. package/dist/components/kbd/kbd.js +18 -0
  94. package/dist/components/kbd/kbd.js.map +1 -0
  95. package/dist/components/kbd/kbd.stories.js +119 -0
  96. package/dist/components/kbd/kbd.stories.js.map +1 -0
  97. package/dist/components/search/index.d.ts +7 -0
  98. package/dist/components/search/index.d.ts.map +1 -0
  99. package/dist/components/search/index.js +8 -0
  100. package/dist/components/search/index.js.map +1 -0
  101. package/dist/components/search/search-context.d.ts +11 -0
  102. package/dist/components/search/search-context.d.ts.map +1 -0
  103. package/dist/components/search/search-context.js +56 -0
  104. package/dist/components/search/search-context.js.map +1 -0
  105. package/dist/components/search/search-inline.d.ts +9 -0
  106. package/dist/components/search/search-inline.d.ts.map +1 -0
  107. package/dist/components/search/search-inline.js +85 -0
  108. package/dist/components/search/search-inline.js.map +1 -0
  109. package/dist/components/search/search-modal.d.ts +25 -0
  110. package/dist/components/search/search-modal.d.ts.map +1 -0
  111. package/dist/components/search/search-modal.js +162 -0
  112. package/dist/components/search/search-modal.js.map +1 -0
  113. package/dist/components/search/search-trigger.d.ts +9 -0
  114. package/dist/components/search/search-trigger.d.ts.map +1 -0
  115. package/dist/components/search/search-trigger.js +37 -0
  116. package/dist/components/search/search-trigger.js.map +1 -0
  117. package/dist/components/search/search-variants.d.ts +14 -0
  118. package/dist/components/search/search-variants.d.ts.map +1 -0
  119. package/dist/components/search/search-variants.js +90 -0
  120. package/dist/components/search/search-variants.js.map +1 -0
  121. package/dist/components/search/search.d.ts +11 -0
  122. package/dist/components/search/search.d.ts.map +1 -0
  123. package/dist/components/search/search.js +35 -0
  124. package/dist/components/search/search.js.map +1 -0
  125. package/dist/components/search/search.stories.js +630 -0
  126. package/dist/components/search/search.stories.js.map +1 -0
  127. package/dist/components/select/index.d.ts +2 -0
  128. package/dist/components/select/index.d.ts.map +1 -0
  129. package/dist/components/select/index.js +3 -0
  130. package/dist/components/select/index.js.map +1 -0
  131. package/dist/components/select/select.d.ts +25 -0
  132. package/dist/components/select/select.d.ts.map +1 -0
  133. package/dist/components/select/select.js +153 -0
  134. package/dist/components/select/select.js.map +1 -0
  135. package/dist/components/select/select.stories.js +393 -0
  136. package/dist/components/select/select.stories.js.map +1 -0
  137. package/dist/components/skeleton/index.d.ts +2 -0
  138. package/dist/components/skeleton/index.d.ts.map +1 -0
  139. package/dist/components/skeleton/index.js +3 -0
  140. package/dist/components/skeleton/index.js.map +1 -0
  141. package/dist/components/skeleton/skeleton.d.ts +5 -0
  142. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  143. package/dist/components/skeleton/skeleton.js +11 -0
  144. package/dist/components/skeleton/skeleton.js.map +1 -0
  145. package/dist/components/skeleton/skeleton.stories.js +345 -0
  146. package/dist/components/skeleton/skeleton.stories.js.map +1 -0
  147. package/dist/components/table/data-table.d.ts +70 -0
  148. package/dist/components/table/data-table.d.ts.map +1 -0
  149. package/dist/components/table/data-table.js +159 -0
  150. package/dist/components/table/data-table.js.map +1 -0
  151. package/dist/components/table/index.d.ts +6 -0
  152. package/dist/components/table/index.d.ts.map +1 -0
  153. package/dist/components/table/index.js +6 -0
  154. package/dist/components/table/index.js.map +1 -0
  155. package/dist/components/table/table-column-header.d.ts +79 -0
  156. package/dist/components/table/table-column-header.d.ts.map +1 -0
  157. package/dist/components/table/table-column-header.js +99 -0
  158. package/dist/components/table/table-column-header.js.map +1 -0
  159. package/dist/components/table/table-pagination.d.ts +53 -0
  160. package/dist/components/table/table-pagination.d.ts.map +1 -0
  161. package/dist/components/table/table-pagination.js +139 -0
  162. package/dist/components/table/table-pagination.js.map +1 -0
  163. package/dist/components/table/table.d.ts +11 -0
  164. package/dist/components/table/table.d.ts.map +1 -0
  165. package/dist/components/table/table.js +64 -0
  166. package/dist/components/table/table.js.map +1 -0
  167. package/dist/components/table/table.stories.columns.d.ts +24 -0
  168. package/dist/components/table/table.stories.columns.d.ts.map +1 -0
  169. package/dist/components/table/table.stories.columns.js +310 -0
  170. package/dist/components/table/table.stories.columns.js.map +1 -0
  171. package/dist/components/table/table.stories.components.d.ts +14 -0
  172. package/dist/components/table/table.stories.components.d.ts.map +1 -0
  173. package/dist/components/table/table.stories.components.js +107 -0
  174. package/dist/components/table/table.stories.components.js.map +1 -0
  175. package/dist/components/table/table.stories.data.d.ts +54 -0
  176. package/dist/components/table/table.stories.data.d.ts.map +1 -0
  177. package/dist/components/table/table.stories.data.js +122 -0
  178. package/dist/components/table/table.stories.data.js.map +1 -0
  179. package/dist/components/table/table.stories.js +302 -0
  180. package/dist/components/table/table.stories.js.map +1 -0
  181. package/dist/styles.css +1 -1
  182. package/index.css +48 -0
  183. package/package.json +3 -2
  184. package/src/components/avatar/avatar.tsx +1 -1
  185. package/src/components/button-group/button-group.stories.tsx +361 -0
  186. package/src/components/button-group/button-group.tsx +111 -0
  187. package/src/components/button-group/index.ts +1 -0
  188. package/src/components/code-block/code-block-footer.tsx +8 -1
  189. package/src/components/command/command.stories.tsx +133 -0
  190. package/src/components/command/command.tsx +265 -0
  191. package/src/components/command/index.ts +1 -0
  192. package/src/components/dashboard/components/analytics-content.tsx +102 -0
  193. package/src/components/dashboard/components/animated-logo.tsx +25 -0
  194. package/src/components/dashboard/components/complete-setup-button.tsx +30 -0
  195. package/src/components/dashboard/components/jobs-content.tsx +51 -0
  196. package/src/components/dashboard/components/mobile-menu.tsx +50 -0
  197. package/src/components/dashboard/components/organization-selector.tsx +51 -0
  198. package/src/components/dashboard/components/top-menu.tsx +26 -0
  199. package/src/components/dashboard/components/topbar-button.tsx +27 -0
  200. package/src/components/dashboard/components/topbar.tsx +40 -0
  201. package/src/components/dashboard/components/user-profile.tsx +90 -0
  202. package/src/components/dashboard/dashboard.stories.tsx +25 -0
  203. package/src/components/dashboard/dashboard.tsx +61 -0
  204. package/src/components/dashboard/index.ts +1 -0
  205. package/src/components/form/form.stories.tsx +5 -0
  206. package/src/components/index.ts +7 -0
  207. package/src/components/kbd/index.ts +1 -0
  208. package/src/components/kbd/kbd.stories.tsx +64 -0
  209. package/src/components/kbd/kbd.tsx +32 -0
  210. package/src/components/search/index.ts +28 -0
  211. package/src/components/search/search-context.tsx +78 -0
  212. package/src/components/search/search-inline.tsx +107 -0
  213. package/src/components/search/search-modal.tsx +198 -0
  214. package/src/components/search/search-trigger.tsx +47 -0
  215. package/src/components/search/search-variants.ts +88 -0
  216. package/src/components/search/search.stories.tsx +392 -0
  217. package/src/components/search/search.tsx +47 -0
  218. package/src/components/select/index.ts +1 -0
  219. package/src/components/select/select.stories.tsx +207 -0
  220. package/src/components/select/select.tsx +220 -0
  221. package/src/components/skeleton/index.ts +1 -0
  222. package/src/components/skeleton/skeleton.stories.tsx +178 -0
  223. package/src/components/skeleton/skeleton.tsx +14 -0
  224. package/src/components/table/data-table.tsx +254 -0
  225. package/src/components/table/index.ts +5 -0
  226. package/src/components/table/table-column-header.tsx +141 -0
  227. package/src/components/table/table-pagination.tsx +161 -0
  228. package/src/components/table/table.stories.columns.tsx +198 -0
  229. package/src/components/table/table.stories.components.tsx +104 -0
  230. package/src/components/table/table.stories.data.ts +117 -0
  231. package/src/components/table/table.stories.tsx +256 -0
  232. package/src/components/table/table.tsx +95 -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,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
+ }
@@ -0,0 +1,50 @@
1
+ import {Button} from 'components/button';
2
+ import {
3
+ DropdownMenu,
4
+ DropdownMenuContent,
5
+ DropdownMenuItem,
6
+ DropdownMenuSeparator,
7
+ DropdownMenuTrigger,
8
+ } from 'components/dropdown-menu';
9
+ import {Icon} from 'components/icon';
10
+ import {ShinyText} from 'components/shiny-text';
11
+ import {useResolvedTheme} from 'hooks/useResolvedTheme';
12
+ import {ShipfoxLoader} from 'shipfox-loader-react';
13
+
14
+ export function MobileMenu() {
15
+ const resolvedTheme = useResolvedTheme();
16
+ return (
17
+ <DropdownMenu>
18
+ <DropdownMenuTrigger asChild>
19
+ <Button
20
+ type="button"
21
+ variant="transparent"
22
+ className="flex md:hidden items-center justify-center shrink-0 w-40 h-40 bg-background-subtle-base hover:bg-background-neutral-hover transition-colors rounded-none border-l border-border-neutral-strong"
23
+ aria-label="Menu"
24
+ >
25
+ <Icon name="menuLine" className="size-18 text-foreground-neutral-subtle" />
26
+ </Button>
27
+ </DropdownMenuTrigger>
28
+ <DropdownMenuContent align="end" className="w-200">
29
+ <DropdownMenuItem>
30
+ <div className="flex items-center gap-8">
31
+ <ShipfoxLoader
32
+ size={13}
33
+ animation="circular"
34
+ color={resolvedTheme === 'dark' ? 'white' : 'orange'}
35
+ background={resolvedTheme === 'dark' ? 'dark' : 'light'}
36
+ />
37
+ <ShinyText
38
+ text="Complete setup"
39
+ className="flex-1 text-sm font-medium leading-20 text-foreground-neutral-base truncate text-left"
40
+ />
41
+ </div>
42
+ </DropdownMenuItem>
43
+ <DropdownMenuSeparator />
44
+ <DropdownMenuItem icon="searchLine">Search</DropdownMenuItem>
45
+ <DropdownMenuItem icon="questionLine">Help</DropdownMenuItem>
46
+ <DropdownMenuItem icon="notification3Line">Notifications</DropdownMenuItem>
47
+ </DropdownMenuContent>
48
+ </DropdownMenu>
49
+ );
50
+ }
@@ -0,0 +1,51 @@
1
+ import {Avatar} from 'components/avatar';
2
+ import {Button} from 'components/button';
3
+ import {Icon} from 'components/icon';
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectSeparator,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from 'components/select';
12
+
13
+ export function OrganizationSelector() {
14
+ return (
15
+ <Select defaultValue="stripe">
16
+ <SelectTrigger className="w-200 h-40 shadow-none bg-background-neutral-base hover:bg-background-neutral-hover rounded-none gap-8 pl-12 border-l min-[321px]:border-r border-border-neutral-strong">
17
+ <div className="flex items-center gap-8 flex-1 min-w-0">
18
+ <SelectValue placeholder="Select organization" />
19
+ </div>
20
+ </SelectTrigger>
21
+ <SelectContent>
22
+ <SelectItem value="stripe">
23
+ <div className="flex items-center gap-8">
24
+ <Avatar size="3xs" content="logo" logoName="stripe" radius="rounded" />
25
+ <span>Stripe</span>
26
+ </div>
27
+ </SelectItem>
28
+ <SelectItem value="shipfox">
29
+ <div className="flex items-center gap-8">
30
+ <Avatar size="3xs" content="logo" logoName="shipfox" radius="rounded" />
31
+ <span>Shipfox</span>
32
+ </div>
33
+ </SelectItem>
34
+ <SelectItem value="github">
35
+ <div className="flex items-center gap-8">
36
+ <Avatar size="3xs" content="logo" logoName="github" radius="rounded" />
37
+ <span>GitHub</span>
38
+ </div>
39
+ </SelectItem>
40
+ <SelectSeparator />
41
+ <Button
42
+ variant="transparent"
43
+ className="w-full justify-start text-foreground-neutral-subtle"
44
+ >
45
+ <Icon name="addLine" className="size-16 shrink-0" />
46
+ <span>New organization</span>
47
+ </Button>
48
+ </SelectContent>
49
+ </Select>
50
+ );
51
+ }
@@ -0,0 +1,26 @@
1
+ import {Tabs, TabsList, TabsTrigger} from 'components/tabs';
2
+
3
+ export function TopMenu({
4
+ activeTab,
5
+ onTabChange,
6
+ }: {
7
+ activeTab: string;
8
+ onTabChange: (tab: string) => void;
9
+ }) {
10
+ return (
11
+ <div className="flex items-center justify-between w-full">
12
+ <div className="flex items-center flex-1 min-w-0 pl-12 md:pl-20 pr-8">
13
+ <Tabs value={activeTab} onValueChange={onTabChange}>
14
+ <TabsList className="h-48 gap-8 md:gap-12">
15
+ <TabsTrigger value="analytics" className="text-sm font-medium">
16
+ Analytics
17
+ </TabsTrigger>
18
+ <TabsTrigger value="jobs" className="text-sm font-medium">
19
+ Jobs
20
+ </TabsTrigger>
21
+ </TabsList>
22
+ </Tabs>
23
+ </div>
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,27 @@
1
+ import {Button} from 'components/button';
2
+ import {Icon, type IconName} from 'components/icon';
3
+ import {cn} from 'utils/cn';
4
+
5
+ export function TopbarButton({
6
+ className,
7
+ icon,
8
+ label,
9
+ }: {
10
+ className?: string;
11
+ icon: IconName;
12
+ label?: string;
13
+ }) {
14
+ return (
15
+ <Button
16
+ type="button"
17
+ variant="transparent"
18
+ className={cn(
19
+ 'flex items-center justify-center overflow-hidden shrink-0 w-40 h-40 bg-background-subtle-base hover:bg-background-neutral-hover transition-colors rounded-none border-l border-border-neutral-strong',
20
+ className,
21
+ )}
22
+ aria-label={label ?? undefined}
23
+ >
24
+ <Icon name={icon} className="size-18 text-foreground-neutral-subtle" />
25
+ </Button>
26
+ );
27
+ }
@@ -0,0 +1,40 @@
1
+ import {cn} from 'utils/cn';
2
+ import {CompleteSetupButton} from './complete-setup-button';
3
+ import {MobileMenu} from './mobile-menu';
4
+ import {OrganizationSelector} from './organization-selector';
5
+ import {TopbarButton} from './topbar-button';
6
+ import {UserProfile} from './user-profile';
7
+
8
+ export function Topbar({hideLogo = false}: {hideLogo?: boolean}) {
9
+ return (
10
+ <div className="flex flex-col items-start w-full bg-background-subtle-base">
11
+ <div className="flex items-center justify-between w-full shrink-0 border-b border-border-neutral-strong">
12
+ <div className="flex items-center flex-1 min-w-0">
13
+ <div className={cn('shrink-0', hideLogo ? 'opacity-0' : 'opacity-100')}>
14
+ <TopbarButton icon="shipfox" label="Shipfox" className="border-none" />
15
+ </div>
16
+ <OrganizationSelector />
17
+ <div className="hidden md:block flex-1 h-40 bg-background-subtle-base" />
18
+ </div>
19
+
20
+ <CompleteSetupButton className="hidden md:flex" />
21
+
22
+ <div className="hidden md:block">
23
+ <TopbarButton icon="searchLine" label="Search" />
24
+ </div>
25
+
26
+ <div className="hidden md:block">
27
+ <TopbarButton icon="questionLine" label="Help" />
28
+ </div>
29
+
30
+ <div className="hidden md:block">
31
+ <TopbarButton icon="notification3Line" label="Notifications" />
32
+ </div>
33
+
34
+ <MobileMenu />
35
+
36
+ <UserProfile />
37
+ </div>
38
+ </div>
39
+ );
40
+ }