@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.
- package/.storybook/preview.tsx +7 -0
- package/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-check.log +2 -2
- package/.turbo/turbo-type.log +1 -1
- package/CHANGELOG.md +16 -0
- package/dist/components/avatar/avatar.js +1 -1
- package/dist/components/avatar/avatar.js.map +1 -1
- package/dist/components/button-group/button-group.d.ts +17 -0
- package/dist/components/button-group/button-group.d.ts.map +1 -0
- package/dist/components/button-group/button-group.js +74 -0
- package/dist/components/button-group/button-group.js.map +1 -0
- package/dist/components/button-group/button-group.stories.js +644 -0
- package/dist/components/button-group/button-group.stories.js.map +1 -0
- package/dist/components/button-group/index.d.ts +2 -0
- package/dist/components/button-group/index.d.ts.map +1 -0
- package/dist/components/button-group/index.js +3 -0
- package/dist/components/button-group/index.js.map +1 -0
- package/dist/components/code-block/code-block-footer.d.ts.map +1 -1
- package/dist/components/code-block/code-block-footer.js +13 -5
- package/dist/components/code-block/code-block-footer.js.map +1 -1
- package/dist/components/command/command.d.ts +28 -0
- package/dist/components/command/command.d.ts.map +1 -0
- package/dist/components/command/command.js +190 -0
- package/dist/components/command/command.js.map +1 -0
- package/dist/components/command/command.stories.js +228 -0
- package/dist/components/command/command.stories.js.map +1 -0
- package/dist/components/command/index.d.ts +2 -0
- package/dist/components/command/index.d.ts.map +1 -0
- package/dist/components/command/index.js +3 -0
- package/dist/components/command/index.js.map +1 -0
- 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/dashboard/components/analytics-content.d.ts +2 -0
- package/dist/components/dashboard/components/analytics-content.d.ts.map +1 -0
- package/dist/components/dashboard/components/analytics-content.js +180 -0
- package/dist/components/dashboard/components/analytics-content.js.map +1 -0
- package/dist/components/dashboard/components/animated-logo.d.ts +4 -0
- package/dist/components/dashboard/components/animated-logo.d.ts.map +1 -0
- package/dist/components/dashboard/components/animated-logo.js +23 -0
- package/dist/components/dashboard/components/animated-logo.js.map +1 -0
- package/dist/components/dashboard/components/complete-setup-button.d.ts +4 -0
- package/dist/components/dashboard/components/complete-setup-button.d.ts.map +1 -0
- package/dist/components/dashboard/components/complete-setup-button.js +28 -0
- package/dist/components/dashboard/components/complete-setup-button.js.map +1 -0
- package/dist/components/dashboard/components/jobs-content.d.ts +2 -0
- package/dist/components/dashboard/components/jobs-content.d.ts.map +1 -0
- package/dist/components/dashboard/components/jobs-content.js +69 -0
- package/dist/components/dashboard/components/jobs-content.js.map +1 -0
- package/dist/components/dashboard/components/mobile-menu.d.ts +2 -0
- package/dist/components/dashboard/components/mobile-menu.d.ts.map +1 -0
- package/dist/components/dashboard/components/mobile-menu.js +65 -0
- package/dist/components/dashboard/components/mobile-menu.js.map +1 -0
- package/dist/components/dashboard/components/organization-selector.d.ts +2 -0
- package/dist/components/dashboard/components/organization-selector.d.ts.map +1 -0
- package/dist/components/dashboard/components/organization-selector.js +92 -0
- package/dist/components/dashboard/components/organization-selector.js.map +1 -0
- package/dist/components/dashboard/components/top-menu.d.ts +5 -0
- package/dist/components/dashboard/components/top-menu.d.ts.map +1 -0
- package/dist/components/dashboard/components/top-menu.js +31 -0
- package/dist/components/dashboard/components/top-menu.js.map +1 -0
- package/dist/components/dashboard/components/topbar-button.d.ts +7 -0
- package/dist/components/dashboard/components/topbar-button.d.ts.map +1 -0
- package/dist/components/dashboard/components/topbar-button.js +18 -0
- package/dist/components/dashboard/components/topbar-button.js.map +1 -0
- package/dist/components/dashboard/components/topbar.d.ts +4 -0
- package/dist/components/dashboard/components/topbar.d.ts.map +1 -0
- package/dist/components/dashboard/components/topbar.js +62 -0
- package/dist/components/dashboard/components/topbar.js.map +1 -0
- package/dist/components/dashboard/components/user-profile.d.ts +2 -0
- package/dist/components/dashboard/components/user-profile.d.ts.map +1 -0
- package/dist/components/dashboard/components/user-profile.js +146 -0
- package/dist/components/dashboard/components/user-profile.js.map +1 -0
- package/dist/components/dashboard/dashboard.d.ts +2 -0
- package/dist/components/dashboard/dashboard.d.ts.map +1 -0
- package/dist/components/dashboard/dashboard.js +70 -0
- package/dist/components/dashboard/dashboard.js.map +1 -0
- package/dist/components/dashboard/dashboard.stories.js +23 -0
- package/dist/components/dashboard/dashboard.stories.js.map +1 -0
- package/dist/components/dashboard/index.d.ts +2 -0
- package/dist/components/dashboard/index.d.ts.map +1 -0
- package/dist/components/dashboard/index.js +3 -0
- package/dist/components/dashboard/index.js.map +1 -0
- package/dist/components/form/form.stories.js +6 -1
- package/dist/components/form/form.stories.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 +9 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +9 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/kbd/index.d.ts +2 -0
- package/dist/components/kbd/index.d.ts.map +1 -0
- package/dist/components/kbd/index.js +3 -0
- package/dist/components/kbd/index.js.map +1 -0
- package/dist/components/kbd/kbd.d.ts +7 -0
- package/dist/components/kbd/kbd.d.ts.map +1 -0
- package/dist/components/kbd/kbd.js +18 -0
- package/dist/components/kbd/kbd.js.map +1 -0
- package/dist/components/kbd/kbd.stories.js +119 -0
- package/dist/components/kbd/kbd.stories.js.map +1 -0
- package/dist/components/modal/modal.stories.js +227 -168
- package/dist/components/modal/modal.stories.js.map +1 -1
- package/dist/components/search/index.d.ts +7 -0
- package/dist/components/search/index.d.ts.map +1 -0
- package/dist/components/search/index.js +8 -0
- package/dist/components/search/index.js.map +1 -0
- package/dist/components/search/search-context.d.ts +11 -0
- package/dist/components/search/search-context.d.ts.map +1 -0
- package/dist/components/search/search-context.js +56 -0
- package/dist/components/search/search-context.js.map +1 -0
- package/dist/components/search/search-inline.d.ts +9 -0
- package/dist/components/search/search-inline.d.ts.map +1 -0
- package/dist/components/search/search-inline.js +85 -0
- package/dist/components/search/search-inline.js.map +1 -0
- package/dist/components/search/search-modal.d.ts +25 -0
- package/dist/components/search/search-modal.d.ts.map +1 -0
- package/dist/components/search/search-modal.js +162 -0
- package/dist/components/search/search-modal.js.map +1 -0
- package/dist/components/search/search-trigger.d.ts +9 -0
- package/dist/components/search/search-trigger.d.ts.map +1 -0
- package/dist/components/search/search-trigger.js +37 -0
- package/dist/components/search/search-trigger.js.map +1 -0
- package/dist/components/search/search-variants.d.ts +14 -0
- package/dist/components/search/search-variants.d.ts.map +1 -0
- package/dist/components/search/search-variants.js +90 -0
- package/dist/components/search/search-variants.js.map +1 -0
- package/dist/components/search/search.d.ts +11 -0
- package/dist/components/search/search.d.ts.map +1 -0
- package/dist/components/search/search.js +35 -0
- package/dist/components/search/search.js.map +1 -0
- package/dist/components/search/search.stories.js +630 -0
- package/dist/components/search/search.stories.js.map +1 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.d.ts.map +1 -0
- package/dist/components/select/index.js +3 -0
- package/dist/components/select/index.js.map +1 -0
- package/dist/components/select/select.d.ts +25 -0
- package/dist/components/select/select.d.ts.map +1 -0
- package/dist/components/select/select.js +153 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/select/select.stories.js +393 -0
- package/dist/components/select/select.stories.js.map +1 -0
- 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/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.d.ts.map +1 -0
- package/dist/components/skeleton/index.js +3 -0
- package/dist/components/skeleton/index.js.map +1 -0
- package/dist/components/skeleton/skeleton.d.ts +5 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton/skeleton.js +11 -0
- package/dist/components/skeleton/skeleton.js.map +1 -0
- package/dist/components/skeleton/skeleton.stories.js +345 -0
- package/dist/components/skeleton/skeleton.stories.js.map +1 -0
- package/dist/components/table/data-table.d.ts +70 -0
- package/dist/components/table/data-table.d.ts.map +1 -0
- package/dist/components/table/data-table.js +159 -0
- package/dist/components/table/data-table.js.map +1 -0
- package/dist/components/table/index.d.ts +6 -0
- package/dist/components/table/index.d.ts.map +1 -0
- package/dist/components/table/index.js +6 -0
- package/dist/components/table/index.js.map +1 -0
- package/dist/components/table/table-column-header.d.ts +79 -0
- package/dist/components/table/table-column-header.d.ts.map +1 -0
- package/dist/components/table/table-column-header.js +99 -0
- package/dist/components/table/table-column-header.js.map +1 -0
- package/dist/components/table/table-pagination.d.ts +53 -0
- package/dist/components/table/table-pagination.d.ts.map +1 -0
- package/dist/components/table/table-pagination.js +139 -0
- package/dist/components/table/table-pagination.js.map +1 -0
- package/dist/components/table/table.d.ts +11 -0
- package/dist/components/table/table.d.ts.map +1 -0
- package/dist/components/table/table.js +64 -0
- package/dist/components/table/table.js.map +1 -0
- package/dist/components/table/table.stories.columns.d.ts +24 -0
- package/dist/components/table/table.stories.columns.d.ts.map +1 -0
- package/dist/components/table/table.stories.columns.js +310 -0
- package/dist/components/table/table.stories.columns.js.map +1 -0
- package/dist/components/table/table.stories.components.d.ts +14 -0
- package/dist/components/table/table.stories.components.d.ts.map +1 -0
- package/dist/components/table/table.stories.components.js +107 -0
- package/dist/components/table/table.stories.components.js.map +1 -0
- package/dist/components/table/table.stories.data.d.ts +54 -0
- package/dist/components/table/table.stories.data.d.ts.map +1 -0
- package/dist/components/table/table.stories.data.js +122 -0
- package/dist/components/table/table.stories.data.js.map +1 -0
- package/dist/components/table/table.stories.js +302 -0
- package/dist/components/table/table.stories.js.map +1 -0
- 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/index.css +79 -0
- package/package.json +6 -2
- package/src/components/avatar/avatar.tsx +1 -1
- package/src/components/button-group/button-group.stories.tsx +361 -0
- package/src/components/button-group/button-group.tsx +111 -0
- package/src/components/button-group/index.ts +1 -0
- package/src/components/code-block/code-block-footer.tsx +19 -2
- package/src/components/command/command.stories.tsx +133 -0
- package/src/components/command/command.tsx +265 -0
- package/src/components/command/index.ts +1 -0
- 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/dashboard/components/analytics-content.tsx +102 -0
- package/src/components/dashboard/components/animated-logo.tsx +25 -0
- package/src/components/dashboard/components/complete-setup-button.tsx +30 -0
- package/src/components/dashboard/components/jobs-content.tsx +51 -0
- package/src/components/dashboard/components/mobile-menu.tsx +50 -0
- package/src/components/dashboard/components/organization-selector.tsx +51 -0
- package/src/components/dashboard/components/top-menu.tsx +26 -0
- package/src/components/dashboard/components/topbar-button.tsx +27 -0
- package/src/components/dashboard/components/topbar.tsx +40 -0
- package/src/components/dashboard/components/user-profile.tsx +90 -0
- package/src/components/dashboard/dashboard.stories.tsx +25 -0
- package/src/components/dashboard/dashboard.tsx +61 -0
- package/src/components/dashboard/index.ts +1 -0
- package/src/components/form/form.stories.tsx +5 -0
- package/src/components/icon/icon.tsx +7 -3
- package/src/components/index.ts +9 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.stories.tsx +64 -0
- package/src/components/kbd/kbd.tsx +32 -0
- package/src/components/modal/modal.stories.tsx +58 -4
- package/src/components/search/index.ts +28 -0
- package/src/components/search/search-context.tsx +78 -0
- package/src/components/search/search-inline.tsx +107 -0
- package/src/components/search/search-modal.tsx +198 -0
- package/src/components/search/search-trigger.tsx +47 -0
- package/src/components/search/search-variants.ts +88 -0
- package/src/components/search/search.stories.tsx +392 -0
- package/src/components/search/search.tsx +47 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.stories.tsx +207 -0
- package/src/components/select/select.tsx +220 -0
- package/src/components/shiny-text/index.ts +1 -0
- package/src/components/shiny-text/shiny-text.tsx +21 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.stories.tsx +178 -0
- package/src/components/skeleton/skeleton.tsx +14 -0
- package/src/components/table/data-table.tsx +254 -0
- package/src/components/table/index.ts +5 -0
- package/src/components/table/table-column-header.tsx +141 -0
- package/src/components/table/table-pagination.tsx +161 -0
- package/src/components/table/table.stories.columns.tsx +198 -0
- package/src/components/table/table.stories.components.tsx +104 -0
- package/src/components/table/table.stories.data.ts +117 -0
- package/src/components/table/table.stories.tsx +256 -0
- package/src/components/table/table.tsx +95 -0
- 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
|
+
}
|