@shipfox/react-ui 0.11.0 → 0.13.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/.turbo/turbo-build.log +6 -6
- package/.turbo/turbo-check.log +2 -2
- package/.turbo/turbo-type.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/components/badge/index.d.ts +4 -4
- package/dist/components/badge/index.d.ts.map +1 -1
- package/dist/components/badge/index.js +4 -4
- package/dist/components/badge/index.js.map +1 -1
- package/dist/components/calendar/calendar.d.ts +5 -0
- package/dist/components/calendar/calendar.d.ts.map +1 -0
- package/dist/components/calendar/calendar.js +46 -0
- package/dist/components/calendar/calendar.js.map +1 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/index.js +3 -0
- package/dist/components/calendar/index.js.map +1 -0
- package/dist/components/date-picker/date-picker.d.ts +19 -0
- package/dist/components/date-picker/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker/date-picker.js +114 -0
- package/dist/components/date-picker/date-picker.js.map +1 -0
- package/dist/components/date-picker/date-picker.stories.js +333 -0
- package/dist/components/date-picker/date-picker.stories.js.map +1 -0
- package/dist/components/date-picker/index.d.ts +2 -0
- package/dist/components/date-picker/index.d.ts.map +1 -0
- package/dist/components/date-picker/index.js +3 -0
- package/dist/components/date-picker/index.js.map +1 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.d.ts +24 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.d.ts.map +1 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.js +130 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.js.map +1 -0
- package/dist/components/date-time-range-picker/index.d.ts +2 -0
- package/dist/components/date-time-range-picker/index.d.ts.map +1 -0
- package/dist/components/date-time-range-picker/index.js +3 -0
- package/dist/components/date-time-range-picker/index.js.map +1 -0
- package/dist/components/dropdown-menu/index.d.ts +1 -2
- package/dist/components/dropdown-menu/index.d.ts.map +1 -1
- package/dist/components/dropdown-menu/index.js +1 -1
- package/dist/components/dropdown-menu/index.js.map +1 -1
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/inline-tips/inline-tips.stories.js +5 -0
- package/dist/components/inline-tips/inline-tips.stories.js.map +1 -1
- package/dist/components/item/item.stories.js +15 -8
- package/dist/components/item/item.stories.js.map +1 -1
- package/dist/components/modal/index.d.ts +1 -2
- package/dist/components/modal/index.d.ts.map +1 -1
- package/dist/components/modal/index.js +1 -1
- package/dist/components/modal/index.js.map +1 -1
- package/dist/components/modal/modal.d.ts +2 -1
- package/dist/components/modal/modal.d.ts.map +1 -1
- package/dist/components/modal/modal.js +5 -3
- package/dist/components/modal/modal.js.map +1 -1
- package/dist/components/modal/modal.stories.js +16 -6
- package/dist/components/modal/modal.stories.js.map +1 -1
- package/dist/components/popover/index.d.ts +2 -0
- package/dist/components/popover/index.d.ts.map +1 -0
- package/dist/components/popover/index.js +3 -0
- package/dist/components/popover/index.js.map +1 -0
- package/dist/components/popover/popover.d.ts +10 -0
- package/dist/components/popover/popover.d.ts.map +1 -0
- package/dist/components/popover/popover.js +47 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/tabs/index.d.ts +2 -0
- package/dist/components/tabs/index.d.ts.map +1 -0
- package/dist/components/tabs/index.js +3 -0
- package/dist/components/tabs/index.js.map +1 -0
- package/dist/components/tabs/tabs.d.ts +50 -0
- package/dist/components/tabs/tabs.d.ts.map +1 -0
- package/dist/components/tabs/tabs.js +243 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/components/tabs/tabs.stories.js +179 -0
- package/dist/components/tabs/tabs.stories.js.map +1 -0
- package/dist/components/textarea/textarea.stories.js +8 -2
- package/dist/components/textarea/textarea.stories.js.map +1 -1
- package/dist/components/toast/index.d.ts +2 -2
- package/dist/components/toast/index.d.ts.map +1 -1
- package/dist/components/toast/index.js +2 -2
- package/dist/components/toast/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/debounce.d.ts +2 -0
- package/dist/utils/debounce.d.ts.map +1 -0
- package/dist/utils/debounce.js +13 -0
- package/dist/utils/debounce.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/index.css +3 -0
- package/package.json +1 -1
- package/src/components/badge/index.ts +4 -4
- package/src/components/calendar/calendar.tsx +90 -0
- package/src/components/calendar/index.ts +1 -0
- package/src/components/date-picker/date-picker.stories.tsx +230 -0
- package/src/components/date-picker/date-picker.tsx +179 -0
- package/src/components/date-picker/index.ts +1 -0
- package/src/components/date-time-range-picker/date-time-range-picker.tsx +211 -0
- package/src/components/date-time-range-picker/index.ts +1 -0
- package/src/components/dropdown-menu/index.ts +1 -29
- package/src/components/index.ts +6 -0
- package/src/components/inline-tips/inline-tips.stories.tsx +5 -0
- package/src/components/item/item.stories.tsx +65 -56
- package/src/components/modal/index.ts +1 -23
- package/src/components/modal/modal.stories.tsx +18 -8
- package/src/components/modal/modal.tsx +4 -2
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover/popover.tsx +60 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.stories.tsx +100 -0
- package/src/components/tabs/tabs.tsx +380 -0
- package/src/components/textarea/textarea.stories.tsx +8 -2
- package/src/components/toast/index.ts +2 -2
- package/src/utils/debounce.ts +15 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {useState} from 'react';
|
|
3
|
+
import {Tabs, TabsContent, TabsContents, TabsList, TabsTrigger} from '.';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Tabs',
|
|
7
|
+
component: Tabs,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
},
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
} satisfies Meta<typeof Tabs>;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {defaultValue: 'analytics'} as never,
|
|
19
|
+
render: () => (
|
|
20
|
+
<div className="bg-background-neutral-background p-24 w-[80vw]">
|
|
21
|
+
<Tabs defaultValue="analytics">
|
|
22
|
+
<TabsList className="gap-12 border-b border-neutral-strong">
|
|
23
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
24
|
+
<TabsTrigger value="jobs">Jobs</TabsTrigger>
|
|
25
|
+
</TabsList>
|
|
26
|
+
</Tabs>
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Controlled: Story = {
|
|
32
|
+
args: {value: 'analytics', onValueChange: () => undefined} as never,
|
|
33
|
+
render: () => {
|
|
34
|
+
const [value, setValue] = useState('analytics');
|
|
35
|
+
return (
|
|
36
|
+
<div className="bg-background-neutral-background p-24 w-[80vw]">
|
|
37
|
+
<Tabs value={value} onValueChange={setValue}>
|
|
38
|
+
<TabsList className="gap-12 border-b border-neutral-strong">
|
|
39
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
40
|
+
<TabsTrigger value="jobs">Jobs</TabsTrigger>
|
|
41
|
+
</TabsList>
|
|
42
|
+
<TabsContents>
|
|
43
|
+
<TabsContent value="analytics">
|
|
44
|
+
<div className="py-16">
|
|
45
|
+
<p className="text-foreground-neutral-base">
|
|
46
|
+
Analytics content - Current value: {value}
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
</TabsContent>
|
|
50
|
+
<TabsContent value="jobs">
|
|
51
|
+
<div className="py-16">
|
|
52
|
+
<p className="text-foreground-neutral-base">
|
|
53
|
+
Jobs content - Current value: {value}
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
</TabsContent>
|
|
57
|
+
</TabsContents>
|
|
58
|
+
</Tabs>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const MultipleTabs: Story = {
|
|
65
|
+
args: {defaultValue: 'tab1'} as never,
|
|
66
|
+
render: () => (
|
|
67
|
+
<div className="bg-background-neutral-background p-24 w-[80vw]">
|
|
68
|
+
<Tabs defaultValue="tab1">
|
|
69
|
+
<TabsList className="gap-12 border-b border-neutral-strong">
|
|
70
|
+
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
71
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
72
|
+
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
|
|
73
|
+
<TabsTrigger value="tab4">Tab 4</TabsTrigger>
|
|
74
|
+
</TabsList>
|
|
75
|
+
<TabsContents>
|
|
76
|
+
<TabsContent value="tab1">
|
|
77
|
+
<div className="py-16">
|
|
78
|
+
<p className="text-foreground-neutral-base">Content for Tab 1</p>
|
|
79
|
+
</div>
|
|
80
|
+
</TabsContent>
|
|
81
|
+
<TabsContent value="tab2">
|
|
82
|
+
<div className="py-16">
|
|
83
|
+
<p className="text-foreground-neutral-base">Content for Tab 2</p>
|
|
84
|
+
</div>
|
|
85
|
+
</TabsContent>
|
|
86
|
+
<TabsContent value="tab3">
|
|
87
|
+
<div className="py-16">
|
|
88
|
+
<p className="text-foreground-neutral-base">Content for Tab 3</p>
|
|
89
|
+
</div>
|
|
90
|
+
</TabsContent>
|
|
91
|
+
<TabsContent value="tab4">
|
|
92
|
+
<div className="py-16">
|
|
93
|
+
<p className="text-foreground-neutral-base">Content for Tab 4</p>
|
|
94
|
+
</div>
|
|
95
|
+
</TabsContent>
|
|
96
|
+
</TabsContents>
|
|
97
|
+
</Tabs>
|
|
98
|
+
</div>
|
|
99
|
+
),
|
|
100
|
+
};
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import {type HTMLMotionProps, motion, type Transition} from 'framer-motion';
|
|
2
|
+
import {
|
|
3
|
+
Children,
|
|
4
|
+
type ComponentProps,
|
|
5
|
+
createContext,
|
|
6
|
+
forwardRef,
|
|
7
|
+
isValidElement,
|
|
8
|
+
type ReactElement,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
useCallback,
|
|
11
|
+
useContext,
|
|
12
|
+
useEffect,
|
|
13
|
+
useImperativeHandle,
|
|
14
|
+
useMemo,
|
|
15
|
+
useRef,
|
|
16
|
+
useState,
|
|
17
|
+
} from 'react';
|
|
18
|
+
import {cn} from 'utils/cn';
|
|
19
|
+
import {debounce} from 'utils/debounce';
|
|
20
|
+
|
|
21
|
+
type TabsContextType<T extends string = string> = {
|
|
22
|
+
activeValue: T;
|
|
23
|
+
handleValueChange: (value: T) => void;
|
|
24
|
+
registerTrigger: (value: string, node: HTMLElement | null) => void;
|
|
25
|
+
getTriggerElement: (value: string) => HTMLElement | undefined;
|
|
26
|
+
getAllTriggerValues: () => string[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const TabsContext = createContext<TabsContextType<string> | undefined>(undefined);
|
|
30
|
+
|
|
31
|
+
function useTabs<T extends string = string>(): TabsContextType<T> {
|
|
32
|
+
const context = useContext(TabsContext);
|
|
33
|
+
if (!context) {
|
|
34
|
+
throw new Error('useTabs must be used within a Tabs component');
|
|
35
|
+
}
|
|
36
|
+
return context as unknown as TabsContextType<T>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type BaseTabsProps = ComponentProps<'div'> & {
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type UnControlledTabsProps<T extends string = string> = BaseTabsProps & {
|
|
44
|
+
defaultValue?: T;
|
|
45
|
+
value?: never;
|
|
46
|
+
onValueChange?: never;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type ControlledTabsProps<T extends string = string> = BaseTabsProps & {
|
|
50
|
+
value: T;
|
|
51
|
+
onValueChange?: (value: T) => void;
|
|
52
|
+
defaultValue?: never;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type TabsProps<T extends string = string> = UnControlledTabsProps<T> | ControlledTabsProps<T>;
|
|
56
|
+
|
|
57
|
+
function Tabs<T extends string = string>({
|
|
58
|
+
defaultValue,
|
|
59
|
+
value,
|
|
60
|
+
onValueChange,
|
|
61
|
+
children,
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
}: TabsProps<T>) {
|
|
65
|
+
const [activeValue, setActiveValue] = useState<T | undefined>(defaultValue ?? undefined);
|
|
66
|
+
const triggersRef = useRef(new Map<string, HTMLElement>());
|
|
67
|
+
const initialSet = useRef(false);
|
|
68
|
+
const isControlled = value !== undefined;
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (
|
|
72
|
+
!isControlled &&
|
|
73
|
+
activeValue === undefined &&
|
|
74
|
+
triggersRef.current.size > 0 &&
|
|
75
|
+
!initialSet.current
|
|
76
|
+
) {
|
|
77
|
+
const firstTab = Array.from(triggersRef.current.keys())[0];
|
|
78
|
+
setActiveValue(firstTab as T);
|
|
79
|
+
initialSet.current = true;
|
|
80
|
+
}
|
|
81
|
+
}, [activeValue, isControlled]);
|
|
82
|
+
|
|
83
|
+
const registerTrigger = useCallback(
|
|
84
|
+
(value: string, node: HTMLElement | null) => {
|
|
85
|
+
if (node) {
|
|
86
|
+
triggersRef.current.set(value, node);
|
|
87
|
+
if (!isControlled && activeValue === undefined && !initialSet.current) {
|
|
88
|
+
setActiveValue(value as T);
|
|
89
|
+
initialSet.current = true;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
triggersRef.current.delete(value);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[isControlled, activeValue],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const handleValueChange = useCallback(
|
|
99
|
+
(val: T) => {
|
|
100
|
+
if (!isControlled) setActiveValue(val);
|
|
101
|
+
else onValueChange?.(val);
|
|
102
|
+
},
|
|
103
|
+
[isControlled, onValueChange],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const getTriggerElement = useCallback((val: string) => {
|
|
107
|
+
return triggersRef.current.get(val);
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
const getAllTriggerValues = useCallback(() => {
|
|
111
|
+
return Array.from(triggersRef.current.keys());
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
const resolvedActiveValue: T = useMemo(() => {
|
|
115
|
+
if (value !== undefined) return value;
|
|
116
|
+
if (activeValue !== undefined) return activeValue;
|
|
117
|
+
const firstKey = Array.from(triggersRef.current.keys())[0];
|
|
118
|
+
return (firstKey ?? '') as T;
|
|
119
|
+
}, [value, activeValue]);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<TabsContext.Provider
|
|
123
|
+
value={{
|
|
124
|
+
activeValue: resolvedActiveValue as string,
|
|
125
|
+
handleValueChange: handleValueChange as (value: string) => void,
|
|
126
|
+
registerTrigger,
|
|
127
|
+
getTriggerElement,
|
|
128
|
+
getAllTriggerValues,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<div
|
|
132
|
+
data-slot="tabs"
|
|
133
|
+
className={cn('flex flex-col gap-2', className)}
|
|
134
|
+
{...(props as ComponentProps<'div'>)}
|
|
135
|
+
>
|
|
136
|
+
{children}
|
|
137
|
+
</div>
|
|
138
|
+
</TabsContext.Provider>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type TabsListProps = ComponentProps<'div'> & {
|
|
143
|
+
children: ReactNode;
|
|
144
|
+
activeClassName?: string;
|
|
145
|
+
transition?: Transition;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
function TabsList({
|
|
149
|
+
children,
|
|
150
|
+
className,
|
|
151
|
+
activeClassName,
|
|
152
|
+
transition = {
|
|
153
|
+
type: 'spring',
|
|
154
|
+
stiffness: 400,
|
|
155
|
+
damping: 30,
|
|
156
|
+
},
|
|
157
|
+
...props
|
|
158
|
+
}: TabsListProps) {
|
|
159
|
+
const {activeValue, getTriggerElement} = useTabs();
|
|
160
|
+
const [indicatorStyle, setIndicatorStyle] = useState<{
|
|
161
|
+
left: number;
|
|
162
|
+
width: number;
|
|
163
|
+
} | null>(null);
|
|
164
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
165
|
+
|
|
166
|
+
const updateIndicator = useCallback(() => {
|
|
167
|
+
const activeTrigger = getTriggerElement(activeValue);
|
|
168
|
+
|
|
169
|
+
if (activeTrigger && listRef.current) {
|
|
170
|
+
const listRect = listRef.current.getBoundingClientRect();
|
|
171
|
+
const triggerRect = activeTrigger.getBoundingClientRect();
|
|
172
|
+
setIndicatorStyle({
|
|
173
|
+
left: triggerRect.left - listRect.left,
|
|
174
|
+
width: triggerRect.width,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}, [activeValue, getTriggerElement]);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const debouncedUpdate = debounce(updateIndicator, 100);
|
|
181
|
+
window.addEventListener('resize', debouncedUpdate);
|
|
182
|
+
requestAnimationFrame(updateIndicator);
|
|
183
|
+
|
|
184
|
+
return () => {
|
|
185
|
+
window.removeEventListener('resize', debouncedUpdate);
|
|
186
|
+
};
|
|
187
|
+
}, [updateIndicator]);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div
|
|
191
|
+
ref={listRef}
|
|
192
|
+
role="tablist"
|
|
193
|
+
data-slot="tabs-list"
|
|
194
|
+
className={cn('relative inline-flex items-center gap-8', className)}
|
|
195
|
+
{...(props as ComponentProps<'div'>)}
|
|
196
|
+
>
|
|
197
|
+
{children}
|
|
198
|
+
{indicatorStyle && (
|
|
199
|
+
<motion.div
|
|
200
|
+
className={cn(
|
|
201
|
+
'absolute bottom-0 h-2 bg-foreground-highlight-interactive',
|
|
202
|
+
activeClassName,
|
|
203
|
+
)}
|
|
204
|
+
initial={false}
|
|
205
|
+
animate={{
|
|
206
|
+
left: indicatorStyle.left,
|
|
207
|
+
width: indicatorStyle.width,
|
|
208
|
+
}}
|
|
209
|
+
transition={transition}
|
|
210
|
+
/>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
type TabsTriggerProps = Omit<HTMLMotionProps<'button'>, 'ref'> & {
|
|
217
|
+
value: string;
|
|
218
|
+
children: ReactNode;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const TabsTrigger = forwardRef<HTMLButtonElement, TabsTriggerProps>(
|
|
222
|
+
({value, children, className, onKeyDown, ...props}, ref) => {
|
|
223
|
+
const {
|
|
224
|
+
activeValue,
|
|
225
|
+
handleValueChange,
|
|
226
|
+
registerTrigger,
|
|
227
|
+
getAllTriggerValues,
|
|
228
|
+
getTriggerElement,
|
|
229
|
+
} = useTabs();
|
|
230
|
+
|
|
231
|
+
const localRef = useRef<HTMLButtonElement | null>(null);
|
|
232
|
+
useImperativeHandle(ref, () => localRef.current as HTMLButtonElement);
|
|
233
|
+
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
registerTrigger(value, localRef.current);
|
|
236
|
+
return () => registerTrigger(value, null);
|
|
237
|
+
}, [value, registerTrigger]);
|
|
238
|
+
|
|
239
|
+
const isActive = activeValue === value;
|
|
240
|
+
|
|
241
|
+
const handleKeyDown = useCallback(
|
|
242
|
+
(event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
243
|
+
onKeyDown?.(event);
|
|
244
|
+
|
|
245
|
+
const allValues = getAllTriggerValues();
|
|
246
|
+
const currentIndex = allValues.indexOf(value);
|
|
247
|
+
|
|
248
|
+
if (currentIndex === -1) return;
|
|
249
|
+
|
|
250
|
+
let targetIndex = currentIndex;
|
|
251
|
+
let shouldPreventDefault = true;
|
|
252
|
+
|
|
253
|
+
switch (event.key) {
|
|
254
|
+
case 'ArrowLeft': {
|
|
255
|
+
targetIndex = currentIndex > 0 ? currentIndex - 1 : allValues.length - 1;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case 'ArrowRight': {
|
|
259
|
+
targetIndex = currentIndex < allValues.length - 1 ? currentIndex + 1 : 0;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case 'Home': {
|
|
263
|
+
targetIndex = 0;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case 'End': {
|
|
267
|
+
targetIndex = allValues.length - 1;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
default: {
|
|
271
|
+
shouldPreventDefault = false;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (shouldPreventDefault) {
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
const targetValue = allValues[targetIndex];
|
|
279
|
+
if (targetValue) {
|
|
280
|
+
handleValueChange(targetValue);
|
|
281
|
+
const targetElement = getTriggerElement(targetValue);
|
|
282
|
+
targetElement?.focus();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
[value, getAllTriggerValues, getTriggerElement, handleValueChange, onKeyDown],
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<motion.button
|
|
291
|
+
ref={localRef}
|
|
292
|
+
data-slot="tabs-trigger"
|
|
293
|
+
role="tab"
|
|
294
|
+
tabIndex={isActive ? 0 : -1}
|
|
295
|
+
whileTap={{scale: 0.95}}
|
|
296
|
+
onClick={() => handleValueChange(value)}
|
|
297
|
+
onKeyDown={handleKeyDown}
|
|
298
|
+
data-state={isActive ? 'active' : 'inactive'}
|
|
299
|
+
aria-selected={isActive}
|
|
300
|
+
aria-controls={`tabpanel-${value}`}
|
|
301
|
+
id={`tab-${value}`}
|
|
302
|
+
className={cn(
|
|
303
|
+
'relative inline-flex cursor-pointer items-center justify-center whitespace-nowrap px-0 py-10 text-sm font-medium transition-colors outline-none focus-visible:shadow-border-interactive-with-active focus-visible:rounded-2 disabled:pointer-events-none disabled:opacity-50',
|
|
304
|
+
isActive ? 'text-foreground-neutral-base' : 'text-foreground-neutral-muted',
|
|
305
|
+
className,
|
|
306
|
+
)}
|
|
307
|
+
{...props}
|
|
308
|
+
>
|
|
309
|
+
{children}
|
|
310
|
+
</motion.button>
|
|
311
|
+
);
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
TabsTrigger.displayName = 'TabsTrigger';
|
|
316
|
+
|
|
317
|
+
type TabsContentsProps = ComponentProps<'div'> & {
|
|
318
|
+
children: ReactNode;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
function TabsContents({children, className, ...props}: TabsContentsProps) {
|
|
322
|
+
const {activeValue} = useTabs();
|
|
323
|
+
const childrenArray = Children.toArray(children);
|
|
324
|
+
|
|
325
|
+
const activeChild = childrenArray.find(
|
|
326
|
+
(child): child is ReactElement<{value: string}> =>
|
|
327
|
+
isValidElement(child) &&
|
|
328
|
+
typeof child.props === 'object' &&
|
|
329
|
+
child.props !== null &&
|
|
330
|
+
'value' in child.props &&
|
|
331
|
+
child.props.value === activeValue,
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<div data-slot="tabs-contents" className={cn(className)} {...(props as ComponentProps<'div'>)}>
|
|
336
|
+
{activeChild}
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
type TabsContentProps = ComponentProps<'div'> & {
|
|
342
|
+
value: string;
|
|
343
|
+
children: ReactNode;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
function TabsContent({children, value, className, ...props}: TabsContentProps) {
|
|
347
|
+
const {activeValue} = useTabs();
|
|
348
|
+
const isActive = activeValue === value;
|
|
349
|
+
|
|
350
|
+
if (!isActive) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<div
|
|
356
|
+
role="tabpanel"
|
|
357
|
+
data-slot="tabs-content"
|
|
358
|
+
aria-labelledby={`tab-${value}`}
|
|
359
|
+
className={cn(className)}
|
|
360
|
+
{...props}
|
|
361
|
+
>
|
|
362
|
+
{children}
|
|
363
|
+
</div>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export {
|
|
368
|
+
Tabs,
|
|
369
|
+
TabsList,
|
|
370
|
+
TabsTrigger,
|
|
371
|
+
TabsContents,
|
|
372
|
+
TabsContent,
|
|
373
|
+
useTabs,
|
|
374
|
+
type TabsContextType,
|
|
375
|
+
type TabsProps,
|
|
376
|
+
type TabsListProps,
|
|
377
|
+
type TabsTriggerProps,
|
|
378
|
+
type TabsContentsProps,
|
|
379
|
+
type TabsContentProps,
|
|
380
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {argosScreenshot} from '@argos-ci/storybook/vitest';
|
|
1
2
|
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
3
|
import {Code, Header} from 'components/typography';
|
|
3
4
|
import {Textarea} from './textarea';
|
|
@@ -25,7 +26,12 @@ export default meta;
|
|
|
25
26
|
|
|
26
27
|
type Story = StoryObj<typeof meta>;
|
|
27
28
|
|
|
28
|
-
export const Default: Story = {
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
play: async (ctx) => {
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
32
|
+
await argosScreenshot(ctx, 'Textarea Default');
|
|
33
|
+
},
|
|
34
|
+
};
|
|
29
35
|
|
|
30
36
|
const variants = ['base', 'component'] as const;
|
|
31
37
|
const sizes = ['base', 'small'] as const;
|
|
@@ -163,7 +169,7 @@ export const DesignMock: Story = {
|
|
|
163
169
|
<Code variant="label" className="text-foreground-neutral-subtle">
|
|
164
170
|
Size=Base (32), State={state.name}, Color={variant.label}
|
|
165
171
|
</Code>
|
|
166
|
-
<div className="w-
|
|
172
|
+
<div className="w-280">
|
|
167
173
|
<Textarea
|
|
168
174
|
placeholder="Placeholder"
|
|
169
175
|
variant={variant.key}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export * from './toast';
|
|
2
|
+
export * from './toast-custom';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function debounce<T extends (...args: never[]) => void>(
|
|
2
|
+
fn: T,
|
|
3
|
+
delay: number,
|
|
4
|
+
): (...args: Parameters<T>) => void {
|
|
5
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
6
|
+
|
|
7
|
+
return (...args: Parameters<T>) => {
|
|
8
|
+
if (timeoutId) {
|
|
9
|
+
clearTimeout(timeoutId);
|
|
10
|
+
}
|
|
11
|
+
timeoutId = setTimeout(() => {
|
|
12
|
+
fn(...args);
|
|
13
|
+
}, delay);
|
|
14
|
+
};
|
|
15
|
+
}
|