@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,78 @@
|
|
|
1
|
+
import {createContext, useCallback, useContext, useEffect, useState} from 'react';
|
|
2
|
+
import {SHORTCUT_KEY_REGEX} from './search-variants';
|
|
3
|
+
|
|
4
|
+
export type SearchContextValue = {
|
|
5
|
+
open: boolean;
|
|
6
|
+
setOpen: (open: boolean) => void;
|
|
7
|
+
searchValue: string;
|
|
8
|
+
setSearchValue: (value: string) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const SearchContext = createContext<SearchContextValue | null>(null);
|
|
12
|
+
|
|
13
|
+
export function useSearchContext() {
|
|
14
|
+
const context = useContext(SearchContext);
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error('Search components must be used within a Search component');
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useControllableState<T>(
|
|
22
|
+
controlledValue: T | undefined,
|
|
23
|
+
defaultValue: T,
|
|
24
|
+
onChange?: (value: T) => void,
|
|
25
|
+
): [T, (value: T) => void] {
|
|
26
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
27
|
+
const isControlled = controlledValue !== undefined;
|
|
28
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
29
|
+
|
|
30
|
+
const setValue = useCallback(
|
|
31
|
+
(newValue: T) => {
|
|
32
|
+
if (!isControlled) {
|
|
33
|
+
setInternalValue(newValue);
|
|
34
|
+
}
|
|
35
|
+
onChange?.(newValue);
|
|
36
|
+
},
|
|
37
|
+
[isControlled, onChange],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return [value, setValue];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useKeyboardShortcut(shortcutKey: string | undefined, onTrigger: () => void) {
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!shortcutKey) return;
|
|
46
|
+
|
|
47
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
48
|
+
const key = shortcutKey.toLowerCase();
|
|
49
|
+
const isMetaKey = key.startsWith('meta+') || key.startsWith('cmd+') || key.startsWith('⌘');
|
|
50
|
+
const isCtrlKey = key.startsWith('ctrl+');
|
|
51
|
+
const targetKey = key.replace(SHORTCUT_KEY_REGEX, '');
|
|
52
|
+
|
|
53
|
+
const shouldTrigger =
|
|
54
|
+
(isMetaKey && e.metaKey && e.key.toLowerCase() === targetKey) ||
|
|
55
|
+
(isCtrlKey && e.ctrlKey && e.key.toLowerCase() === targetKey) ||
|
|
56
|
+
(!isMetaKey && !isCtrlKey && e.key.toLowerCase() === targetKey && !e.metaKey && !e.ctrlKey);
|
|
57
|
+
|
|
58
|
+
if (!shouldTrigger) return;
|
|
59
|
+
|
|
60
|
+
if (!isMetaKey && !isCtrlKey) {
|
|
61
|
+
const target = e.target as HTMLElement;
|
|
62
|
+
if (
|
|
63
|
+
target.tagName === 'INPUT' ||
|
|
64
|
+
target.tagName === 'TEXTAREA' ||
|
|
65
|
+
target.isContentEditable
|
|
66
|
+
) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
onTrigger();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
76
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
77
|
+
}, [shortcutKey, onTrigger]);
|
|
78
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type {VariantProps} from 'class-variance-authority';
|
|
2
|
+
import type {ComponentProps} from 'react';
|
|
3
|
+
import {useCallback, useRef, useState} from 'react';
|
|
4
|
+
import {cn} from 'utils/cn';
|
|
5
|
+
import {Icon} from '../icon';
|
|
6
|
+
import {searchInputVariants} from './search-variants';
|
|
7
|
+
|
|
8
|
+
export type SearchInlineProps = Omit<ComponentProps<'input'>, 'size'> &
|
|
9
|
+
VariantProps<typeof searchInputVariants> & {
|
|
10
|
+
showClearButton?: boolean;
|
|
11
|
+
onClear?: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function SearchInline({
|
|
15
|
+
className,
|
|
16
|
+
variant,
|
|
17
|
+
size,
|
|
18
|
+
radius,
|
|
19
|
+
value,
|
|
20
|
+
onChange,
|
|
21
|
+
onClear,
|
|
22
|
+
showClearButton = true,
|
|
23
|
+
...props
|
|
24
|
+
}: SearchInlineProps) {
|
|
25
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
26
|
+
const [internalValue, setInternalValue] = useState('');
|
|
27
|
+
const isControlled = value !== undefined;
|
|
28
|
+
const inputValue = isControlled ? value : internalValue;
|
|
29
|
+
const hasValue = Boolean(inputValue);
|
|
30
|
+
const isSmall = size === 'small';
|
|
31
|
+
|
|
32
|
+
const handleChange = useCallback(
|
|
33
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
34
|
+
if (!isControlled) {
|
|
35
|
+
setInternalValue(e.target.value);
|
|
36
|
+
}
|
|
37
|
+
onChange?.(e);
|
|
38
|
+
},
|
|
39
|
+
[isControlled, onChange],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const handleClear = useCallback(() => {
|
|
43
|
+
if (!isControlled) {
|
|
44
|
+
setInternalValue('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (onChange && inputRef.current) {
|
|
48
|
+
inputRef.current.value = '';
|
|
49
|
+
const syntheticEvent = {
|
|
50
|
+
target: inputRef.current,
|
|
51
|
+
currentTarget: inputRef.current,
|
|
52
|
+
} as React.ChangeEvent<HTMLInputElement>;
|
|
53
|
+
onChange(syntheticEvent);
|
|
54
|
+
}
|
|
55
|
+
onClear?.();
|
|
56
|
+
inputRef.current?.focus();
|
|
57
|
+
}, [isControlled, onChange, onClear]);
|
|
58
|
+
|
|
59
|
+
const handleKeyDown = useCallback(
|
|
60
|
+
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
61
|
+
if (e.key === 'Escape' && hasValue) {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
handleClear();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[hasValue, handleClear],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
data-slot="search-inline"
|
|
72
|
+
className={cn(searchInputVariants({variant, size, radius}), className)}
|
|
73
|
+
>
|
|
74
|
+
<Icon
|
|
75
|
+
name="searchLine"
|
|
76
|
+
className={cn('shrink-0 text-foreground-neutral-muted', isSmall ? 'size-14' : 'size-16')}
|
|
77
|
+
/>
|
|
78
|
+
<input
|
|
79
|
+
ref={inputRef}
|
|
80
|
+
type="text"
|
|
81
|
+
value={inputValue}
|
|
82
|
+
onChange={handleChange}
|
|
83
|
+
onKeyDown={handleKeyDown}
|
|
84
|
+
className={cn(
|
|
85
|
+
'flex-1 bg-transparent outline-none min-w-0',
|
|
86
|
+
'text-foreground-neutral-base',
|
|
87
|
+
'placeholder:text-foreground-neutral-muted',
|
|
88
|
+
'disabled:cursor-not-allowed disabled:text-foreground-neutral-disabled',
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
{showClearButton && hasValue && (
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={handleClear}
|
|
96
|
+
className={cn(
|
|
97
|
+
'shrink-0 cursor-pointer rounded-4 p-2 -mx-2',
|
|
98
|
+
'text-foreground-neutral-muted hover:text-foreground-neutral-subtle transition-colors',
|
|
99
|
+
)}
|
|
100
|
+
aria-label="Clear search"
|
|
101
|
+
>
|
|
102
|
+
<Icon name="closeLine" className="size-16" />
|
|
103
|
+
</button>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {Command as CommandPrimitive} from 'cmdk';
|
|
2
|
+
import type {ComponentProps, ReactNode} from 'react';
|
|
3
|
+
import {useCallback} from 'react';
|
|
4
|
+
import {cn} from 'utils/cn';
|
|
5
|
+
import {Icon} from '../icon';
|
|
6
|
+
import {Kbd} from '../kbd';
|
|
7
|
+
import {Modal, ModalBody, ModalContent, type ModalContentProps} from '../modal/modal';
|
|
8
|
+
import {useSearchContext} from './search-context';
|
|
9
|
+
|
|
10
|
+
export type SearchContentProps = {
|
|
11
|
+
breakpoint?: string;
|
|
12
|
+
} & Omit<ModalContentProps, 'open' | 'onOpenChange'>;
|
|
13
|
+
|
|
14
|
+
export function SearchContent({
|
|
15
|
+
breakpoint = '(min-width: 768px)',
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
overlayClassName,
|
|
19
|
+
onEscapeKeyDown,
|
|
20
|
+
...props
|
|
21
|
+
}: SearchContentProps) {
|
|
22
|
+
const {open, setOpen, searchValue, setSearchValue} = useSearchContext();
|
|
23
|
+
|
|
24
|
+
const handleEscapeKeyDown = useCallback(
|
|
25
|
+
(event: KeyboardEvent) => {
|
|
26
|
+
if (searchValue) {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
setSearchValue('');
|
|
29
|
+
} else {
|
|
30
|
+
onEscapeKeyDown?.(event);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
[searchValue, setSearchValue, onEscapeKeyDown],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Modal open={open} onOpenChange={setOpen} breakpoint={breakpoint}>
|
|
38
|
+
<ModalContent
|
|
39
|
+
data-slot="search-content"
|
|
40
|
+
className={cn('top-[15%]! translate-y-0!', className)}
|
|
41
|
+
overlayClassName={cn('backdrop-blur-sm', overlayClassName)}
|
|
42
|
+
onEscapeKeyDown={handleEscapeKeyDown}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
<ModalBody className="flex flex-col p-0 min-h-0 overflow-hidden md:overflow-clip">
|
|
46
|
+
{children}
|
|
47
|
+
</ModalBody>
|
|
48
|
+
</ModalContent>
|
|
49
|
+
</Modal>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type SearchInputProps = Omit<
|
|
54
|
+
ComponentProps<typeof CommandPrimitive.Input>,
|
|
55
|
+
'value' | 'onValueChange'
|
|
56
|
+
>;
|
|
57
|
+
|
|
58
|
+
export function SearchInput({className, ...props}: SearchInputProps) {
|
|
59
|
+
const {open, searchValue, setSearchValue} = useSearchContext();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="w-full shrink-0 flex items-center gap-8 border-b border-border-neutral-strong px-16 py-12">
|
|
63
|
+
<Icon name="searchLine" className="size-16 shrink-0 text-foreground-neutral-muted" />
|
|
64
|
+
<CommandPrimitive.Input
|
|
65
|
+
data-slot="search-input"
|
|
66
|
+
autoFocus={open}
|
|
67
|
+
value={searchValue}
|
|
68
|
+
onValueChange={setSearchValue}
|
|
69
|
+
className={cn(
|
|
70
|
+
'flex-1 bg-transparent text-sm leading-20 outline-none',
|
|
71
|
+
'placeholder:text-foreground-neutral-muted',
|
|
72
|
+
'disabled:cursor-not-allowed disabled:text-foreground-neutral-disabled',
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
<Kbd>Esc</Kbd>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type SearchListProps = ComponentProps<typeof CommandPrimitive.List>;
|
|
83
|
+
|
|
84
|
+
export function SearchList({className, ...props}: SearchListProps) {
|
|
85
|
+
return (
|
|
86
|
+
<CommandPrimitive.List
|
|
87
|
+
data-slot="search-list"
|
|
88
|
+
className={cn(
|
|
89
|
+
'flex-1 min-h-0 w-full overflow-y-auto overflow-x-hidden px-8 py-4 scrollbar',
|
|
90
|
+
'md:max-h-400',
|
|
91
|
+
className,
|
|
92
|
+
)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type SearchEmptyProps = ComponentProps<typeof CommandPrimitive.Empty>;
|
|
99
|
+
|
|
100
|
+
export function SearchEmpty({className, ...props}: SearchEmptyProps) {
|
|
101
|
+
return (
|
|
102
|
+
<CommandPrimitive.Empty
|
|
103
|
+
data-slot="search-empty"
|
|
104
|
+
className={cn('py-32 text-center text-sm text-foreground-neutral-muted', className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type SearchGroupProps = ComponentProps<typeof CommandPrimitive.Group>;
|
|
111
|
+
|
|
112
|
+
export function SearchGroup({className, ...props}: SearchGroupProps) {
|
|
113
|
+
return (
|
|
114
|
+
<CommandPrimitive.Group
|
|
115
|
+
data-slot="search-group"
|
|
116
|
+
className={cn(
|
|
117
|
+
'overflow-hidden',
|
|
118
|
+
'[&_[cmdk-group-heading]]:px-8 [&_[cmdk-group-heading]]:py-4',
|
|
119
|
+
'[&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:leading-20',
|
|
120
|
+
'[&_[cmdk-group-heading]]:text-foreground-neutral-subtle',
|
|
121
|
+
'[&_[cmdk-group-heading]]:select-none',
|
|
122
|
+
className,
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type SearchItemProps = ComponentProps<typeof CommandPrimitive.Item> & {
|
|
130
|
+
icon?: ReactNode;
|
|
131
|
+
description?: string;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export function SearchItem({className, children, icon, description, ...props}: SearchItemProps) {
|
|
135
|
+
return (
|
|
136
|
+
<CommandPrimitive.Item
|
|
137
|
+
data-slot="search-item"
|
|
138
|
+
className={cn(
|
|
139
|
+
'relative flex cursor-pointer select-none items-center gap-12 rounded-8 p-8',
|
|
140
|
+
'text-sm leading-20 text-foreground-neutral-subtle outline-none transition-colors',
|
|
141
|
+
'aria-selected:bg-background-components-hover aria-selected:text-foreground-neutral-base',
|
|
142
|
+
'data-[disabled=true]:pointer-events-none data-[disabled=true]:text-foreground-neutral-disabled',
|
|
143
|
+
className,
|
|
144
|
+
)}
|
|
145
|
+
{...props}
|
|
146
|
+
>
|
|
147
|
+
{icon && <span className="shrink-0 text-foreground-neutral-muted">{icon}</span>}
|
|
148
|
+
<div className="flex-1 min-w-0">
|
|
149
|
+
<div className="truncate">{children}</div>
|
|
150
|
+
{description && (
|
|
151
|
+
<div className="truncate text-xs text-foreground-neutral-muted">{description}</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
</CommandPrimitive.Item>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type SearchSeparatorProps = ComponentProps<typeof CommandPrimitive.Separator>;
|
|
159
|
+
|
|
160
|
+
export function SearchSeparator({className, ...props}: SearchSeparatorProps) {
|
|
161
|
+
return (
|
|
162
|
+
<CommandPrimitive.Separator
|
|
163
|
+
data-slot="search-separator"
|
|
164
|
+
className={cn('my-8 h-px bg-border-neutral-base', className)}
|
|
165
|
+
{...props}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type SearchFooterProps = ComponentProps<'div'>;
|
|
171
|
+
|
|
172
|
+
export function SearchFooter({className, ...props}: SearchFooterProps) {
|
|
173
|
+
return (
|
|
174
|
+
<div
|
|
175
|
+
data-slot="search-footer"
|
|
176
|
+
className={cn(
|
|
177
|
+
'w-full shrink-0 flex items-center justify-end gap-12 px-16 py-12',
|
|
178
|
+
'border-t border-border-neutral-strong',
|
|
179
|
+
'bg-background-components-base',
|
|
180
|
+
className,
|
|
181
|
+
)}
|
|
182
|
+
{...props}
|
|
183
|
+
>
|
|
184
|
+
<div className="flex items-center gap-8">
|
|
185
|
+
<span className="text-xs font-medium text-foreground-neutral-subtle">Navigation</span>
|
|
186
|
+
<div className="flex items-center gap-4">
|
|
187
|
+
<Kbd>↓</Kbd>
|
|
188
|
+
<Kbd>↑</Kbd>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="h-12 w-px bg-border-neutral-strong" />
|
|
192
|
+
<div className="flex items-center gap-8">
|
|
193
|
+
<span className="text-xs font-medium text-foreground-neutral-subtle">Open result</span>
|
|
194
|
+
<Kbd>↵</Kbd>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {VariantProps} from 'class-variance-authority';
|
|
2
|
+
import type {ComponentProps} from 'react';
|
|
3
|
+
import {cn} from 'utils/cn';
|
|
4
|
+
import {Icon} from '../icon';
|
|
5
|
+
import {Kbd} from '../kbd';
|
|
6
|
+
import {useSearchContext} from './search-context';
|
|
7
|
+
import {searchTriggerVariants} from './search-variants';
|
|
8
|
+
|
|
9
|
+
export type SearchTriggerProps = ComponentProps<'button'> &
|
|
10
|
+
VariantProps<typeof searchTriggerVariants> & {
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
shortcut?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function SearchTrigger({
|
|
16
|
+
className,
|
|
17
|
+
variant,
|
|
18
|
+
size,
|
|
19
|
+
radius,
|
|
20
|
+
placeholder = 'Search',
|
|
21
|
+
shortcut = '⌘K',
|
|
22
|
+
...props
|
|
23
|
+
}: SearchTriggerProps) {
|
|
24
|
+
const {setOpen} = useSearchContext();
|
|
25
|
+
const isSmall = size === 'small';
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
data-slot="search-trigger"
|
|
31
|
+
onClick={() => setOpen(true)}
|
|
32
|
+
className={cn(searchTriggerVariants({variant, size, radius}), className)}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
<Icon name="searchLine" className={cn('shrink-0', isSmall ? 'size-14' : 'size-16')} />
|
|
36
|
+
<span className="flex-1 text-left truncate">{placeholder}</span>
|
|
37
|
+
<Kbd
|
|
38
|
+
className={cn(
|
|
39
|
+
isSmall && 'h-16 min-w-16 px-4 text-[10px]',
|
|
40
|
+
radius === 'rounded' && 'rounded-full',
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
{shortcut}
|
|
44
|
+
</Kbd>
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {cva} from 'class-variance-authority';
|
|
2
|
+
import type {Transition} from 'framer-motion';
|
|
3
|
+
|
|
4
|
+
const sharedInputStyles = [
|
|
5
|
+
'inline-flex items-center gap-8',
|
|
6
|
+
'text-sm leading-20',
|
|
7
|
+
'transition-[color,box-shadow,background-color] outline-none',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const variantStyles = {
|
|
11
|
+
primary: {
|
|
12
|
+
base: ['bg-background-field-base', 'shadow-button-neutral'],
|
|
13
|
+
focus: 'focus-within:shadow-border-interactive-with-active',
|
|
14
|
+
hover: 'hover:bg-background-field-hover',
|
|
15
|
+
focusVisible: 'focus-visible:shadow-border-interactive-with-active',
|
|
16
|
+
},
|
|
17
|
+
secondary: {
|
|
18
|
+
base: ['bg-background-field-component', 'border border-border-neutral-strong'],
|
|
19
|
+
focus: 'focus-within:shadow-border-interactive-with-active',
|
|
20
|
+
hover: 'hover:bg-background-field-component-hover',
|
|
21
|
+
focusVisible: 'focus-visible:shadow-border-interactive-with-active',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const sizeStyles = {
|
|
26
|
+
base: 'h-32 px-8 py-6',
|
|
27
|
+
small: 'h-28 px-8 py-4',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const radiusStyles = {
|
|
31
|
+
rounded: 'rounded-full',
|
|
32
|
+
squared: 'rounded-6',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const searchInputVariants = cva(sharedInputStyles, {
|
|
36
|
+
variants: {
|
|
37
|
+
variant: {
|
|
38
|
+
primary: [...variantStyles.primary.base, variantStyles.primary.focus],
|
|
39
|
+
secondary: [...variantStyles.secondary.base, variantStyles.secondary.focus],
|
|
40
|
+
},
|
|
41
|
+
size: sizeStyles,
|
|
42
|
+
radius: radiusStyles,
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: 'primary',
|
|
46
|
+
size: 'base',
|
|
47
|
+
radius: 'squared',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const searchTriggerVariants = cva(
|
|
52
|
+
[
|
|
53
|
+
...sharedInputStyles,
|
|
54
|
+
'cursor-pointer text-foreground-neutral-muted',
|
|
55
|
+
'disabled:pointer-events-none disabled:cursor-not-allowed disabled:text-foreground-neutral-disabled',
|
|
56
|
+
],
|
|
57
|
+
{
|
|
58
|
+
variants: {
|
|
59
|
+
variant: {
|
|
60
|
+
primary: [
|
|
61
|
+
...variantStyles.primary.base,
|
|
62
|
+
variantStyles.primary.hover,
|
|
63
|
+
variantStyles.primary.focusVisible,
|
|
64
|
+
],
|
|
65
|
+
secondary: [
|
|
66
|
+
...variantStyles.secondary.base,
|
|
67
|
+
variantStyles.secondary.hover,
|
|
68
|
+
variantStyles.secondary.focusVisible,
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
size: sizeStyles,
|
|
72
|
+
radius: radiusStyles,
|
|
73
|
+
},
|
|
74
|
+
defaultVariants: {
|
|
75
|
+
variant: 'primary',
|
|
76
|
+
size: 'base',
|
|
77
|
+
radius: 'squared',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export const searchDefaultTransition: Transition = {
|
|
83
|
+
type: 'spring',
|
|
84
|
+
stiffness: 400,
|
|
85
|
+
damping: 30,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const SHORTCUT_KEY_REGEX = /^(meta\+|cmd\+|ctrl\+|⌘\+?)/i;
|