@usefui/components 1.6.0 → 1.7.1
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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +380 -52
- package/dist/index.d.ts +380 -52
- package/dist/index.js +2532 -511
- package/dist/index.mjs +2518 -508
- package/package.json +3 -3
- package/src/__tests__/Avatar.test.tsx +55 -55
- package/src/accordion/Accordion.stories.tsx +6 -4
- package/src/accordion/index.tsx +1 -2
- package/src/avatar/Avatar.stories.tsx +37 -7
- package/src/avatar/index.tsx +90 -19
- package/src/avatar/styles/index.ts +58 -12
- package/src/badge/Badge.stories.tsx +27 -5
- package/src/badge/index.tsx +21 -13
- package/src/badge/styles/index.ts +69 -40
- package/src/button/Button.stories.tsx +40 -27
- package/src/button/index.tsx +13 -9
- package/src/button/styles/index.ts +308 -47
- package/src/card/index.tsx +2 -4
- package/src/checkbox/Checkbox.stories.tsx +72 -33
- package/src/checkbox/index.tsx +8 -6
- package/src/checkbox/styles/index.ts +239 -19
- package/src/collapsible/Collapsible.stories.tsx +6 -4
- package/src/dialog/Dialog.stories.tsx +173 -31
- package/src/dialog/styles/index.ts +13 -8
- package/src/dropdown/Dropdown.stories.tsx +61 -23
- package/src/dropdown/index.tsx +42 -31
- package/src/dropdown/styles/index.ts +30 -19
- package/src/field/Field.stories.tsx +183 -24
- package/src/field/index.tsx +930 -13
- package/src/field/styles/index.ts +246 -14
- package/src/field/types/index.ts +31 -0
- package/src/field/utils/index.ts +201 -0
- package/src/index.ts +2 -1
- package/src/message-bubble/MessageBubble.stories.tsx +59 -12
- package/src/message-bubble/index.tsx +22 -4
- package/src/message-bubble/styles/index.ts +4 -7
- package/src/otp-field/OTPField.stories.tsx +22 -24
- package/src/otp-field/index.tsx +9 -0
- package/src/otp-field/styles/index.ts +114 -16
- package/src/otp-field/types/index.ts +9 -1
- package/src/overlay/styles/index.ts +1 -0
- package/src/ruler/Ruler.stories.tsx +43 -0
- package/src/ruler/constants/index.ts +3 -0
- package/src/ruler/hooks/index.tsx +53 -0
- package/src/ruler/index.tsx +239 -0
- package/src/ruler/styles/index.tsx +154 -0
- package/src/ruler/types/index.ts +17 -0
- package/src/select/Select.stories.tsx +91 -0
- package/src/select/hooks/index.tsx +71 -0
- package/src/select/index.tsx +331 -0
- package/src/select/styles/index.tsx +156 -0
- package/src/shimmer/Shimmer.stories.tsx +6 -4
- package/src/skeleton/index.tsx +7 -6
- package/src/spinner/Spinner.stories.tsx +29 -4
- package/src/spinner/index.tsx +16 -6
- package/src/spinner/styles/index.ts +41 -22
- package/src/switch/Switch.stories.tsx +46 -17
- package/src/switch/index.tsx +5 -8
- package/src/switch/styles/index.ts +45 -45
- package/src/tabs/Tabs.stories.tsx +43 -15
- package/src/text-area/Textarea.stories.tsx +45 -8
- package/src/text-area/index.tsx +9 -6
- package/src/text-area/styles/index.ts +1 -1
- package/src/toggle/Toggle.stories.tsx +6 -4
- package/src/tree/Tree.stories.tsx +6 -4
- package/src/privacy-field/PrivacyField.stories.tsx +0 -29
- package/src/privacy-field/index.tsx +0 -56
- package/src/privacy-field/styles/index.ts +0 -17
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
import { useClickOutside } from "@usefui/hooks";
|
|
6
|
+
import { SelectProvider, useSelect } from "./hooks";
|
|
7
|
+
|
|
8
|
+
import { Wrapper, Trigger, Label, Content, List, Item } from "./styles";
|
|
9
|
+
import { ScrollArea } from "../scrollarea";
|
|
10
|
+
|
|
11
|
+
import { applyDataState } from "../utils";
|
|
12
|
+
import { IButtonProperties } from "../button";
|
|
13
|
+
import {
|
|
14
|
+
IComponentStyling,
|
|
15
|
+
ComponentVariantEnum,
|
|
16
|
+
ComponentShapeEnum,
|
|
17
|
+
ComponentSizeEnum,
|
|
18
|
+
} from "../../../../types";
|
|
19
|
+
|
|
20
|
+
export interface ISelectTriggerProperties extends IButtonProperties {
|
|
21
|
+
error?: boolean;
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ISelectContentProperties
|
|
26
|
+
extends IComponentStyling, React.ComponentPropsWithRef<"ul"> {
|
|
27
|
+
defaultOpen?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ISelectItemProperties
|
|
31
|
+
extends IComponentStyling, Omit<React.ComponentProps<"li">, "onClick"> {
|
|
32
|
+
value?: string;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
onClick?: (
|
|
35
|
+
event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
|
|
36
|
+
) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const SelectRoot = ({ children }: { children: React.ReactElement }) => {
|
|
40
|
+
return <SelectProvider>{children}</SelectProvider>;
|
|
41
|
+
};
|
|
42
|
+
SelectRoot.displayName = "Select.Root";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Select is used to allow users to choose a single value from a list of options.
|
|
46
|
+
*
|
|
47
|
+
* **Best practices:**
|
|
48
|
+
*
|
|
49
|
+
* - Use a clear and descriptive label for the trigger that accurately conveys he purpose of the select.
|
|
50
|
+
* - Ensure that the select can be opened and closed using the keyboard.
|
|
51
|
+
* - Ensure that the select is dismissed when the user clicks outside of it or presses the Esc key.
|
|
52
|
+
* - Wrap this component with `Select.Root` to provide the necessary context.
|
|
53
|
+
*
|
|
54
|
+
* @param {React.ComponentProps<"div">} props - The props for the Select component.
|
|
55
|
+
* @param {ReactNode} props.children - The content to be rendered inside the select.
|
|
56
|
+
* @returns {ReactElement} The Select component.
|
|
57
|
+
*/
|
|
58
|
+
const Select = ({ children }: React.ComponentProps<"div">) => {
|
|
59
|
+
const selectRef = React.useRef<HTMLDivElement | null>(null);
|
|
60
|
+
const { states, methods } = useSelect();
|
|
61
|
+
|
|
62
|
+
const handleClickOutside = () => {
|
|
63
|
+
if (states.open && methods.setOpen) {
|
|
64
|
+
methods.setOpen(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
useClickOutside(
|
|
69
|
+
selectRef as React.RefObject<HTMLElement>,
|
|
70
|
+
handleClickOutside,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return <Wrapper ref={selectRef}>{children}</Wrapper>;
|
|
74
|
+
};
|
|
75
|
+
Select.displayName = "Select";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Select.Trigger is used to control the expansion and collapse of the associated Select.Content component.
|
|
79
|
+
*
|
|
80
|
+
* **Best practices:**
|
|
81
|
+
*
|
|
82
|
+
* - Use a clear and descriptive label that accurately conveys the purpose of the select field.
|
|
83
|
+
* - Ensure that the trigger can be operated using only the keyboard.
|
|
84
|
+
* - Ensure that the focus is properly managed when the trigger is activated.
|
|
85
|
+
* - Use the `error` prop to indicate a validation error state.
|
|
86
|
+
*
|
|
87
|
+
* @param {ISelectTriggerProperties} props - The props for the Select.Trigger component.
|
|
88
|
+
* @param {ComponentVariantEnum} props.variant - The visual variant of the trigger. Defaults to "secondary".
|
|
89
|
+
* @param {ComponentShapeEnum} props.shape - The shape of the trigger. Defaults to "smooth".
|
|
90
|
+
* @param {ComponentSizeEnum} props.sizing - The size of the trigger. Defaults to "medium".
|
|
91
|
+
* @param {boolean} props.error - Whether the trigger should display an error state. Defaults to false.
|
|
92
|
+
* @param {boolean} props.disabled - Whether the trigger is disabled.
|
|
93
|
+
* @param {boolean} props.raw - Define whether the component is styled or not.
|
|
94
|
+
* @param {ReactNode} props.children - The content to be rendered inside the trigger.
|
|
95
|
+
* @returns {ReactElement} The Select.Trigger component.
|
|
96
|
+
*/
|
|
97
|
+
const SelectTrigger = (props: ISelectTriggerProperties) => {
|
|
98
|
+
const {
|
|
99
|
+
raw,
|
|
100
|
+
variant,
|
|
101
|
+
shape,
|
|
102
|
+
sizing,
|
|
103
|
+
error = false,
|
|
104
|
+
disabled,
|
|
105
|
+
children,
|
|
106
|
+
...restProps
|
|
107
|
+
} = props;
|
|
108
|
+
|
|
109
|
+
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
|
110
|
+
const triggerRect = () => triggerRef.current?.getBoundingClientRect();
|
|
111
|
+
|
|
112
|
+
const { id, states, methods } = useSelect();
|
|
113
|
+
const { toggleOpen, setTriggerProps } = methods;
|
|
114
|
+
|
|
115
|
+
const handleClick = () => {
|
|
116
|
+
if (disabled) return;
|
|
117
|
+
if (toggleOpen) toggleOpen();
|
|
118
|
+
if (setTriggerProps) {
|
|
119
|
+
setTriggerProps({
|
|
120
|
+
top: Number(triggerRect()?.top),
|
|
121
|
+
right: Number(triggerRect()?.right),
|
|
122
|
+
bottom: Number(triggerRect()?.bottom),
|
|
123
|
+
left: Number(triggerRect()?.left),
|
|
124
|
+
width: Number(triggerRect()?.width),
|
|
125
|
+
height: Number(triggerRect()?.height),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Trigger
|
|
132
|
+
ref={triggerRef}
|
|
133
|
+
type="button"
|
|
134
|
+
role="combobox"
|
|
135
|
+
id={id.split("|").at(0)}
|
|
136
|
+
onClick={handleClick}
|
|
137
|
+
aria-haspopup="listbox"
|
|
138
|
+
aria-expanded={Boolean(states.open)}
|
|
139
|
+
aria-controls={id.split("|").at(-1)}
|
|
140
|
+
data-state={states.open ? "open" : "closed"}
|
|
141
|
+
data-variant={variant ?? ComponentVariantEnum.Secondary}
|
|
142
|
+
data-shape={shape ?? ComponentShapeEnum.Smooth}
|
|
143
|
+
data-size={sizing ?? ComponentSizeEnum.Medium}
|
|
144
|
+
data-error={error}
|
|
145
|
+
data-raw={Boolean(raw)}
|
|
146
|
+
disabled={disabled}
|
|
147
|
+
{...restProps}
|
|
148
|
+
>
|
|
149
|
+
<Label>{children}</Label>
|
|
150
|
+
</Trigger>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
SelectTrigger.displayName = "Select.Trigger";
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Select.Content contains the list of options associated with the Select.Trigger component.
|
|
157
|
+
*
|
|
158
|
+
* **Best practices:**
|
|
159
|
+
*
|
|
160
|
+
* - Ensure that the content is hidden when the select is collapsed.
|
|
161
|
+
* - Ensure that the content is properly positioned relative to the trigger,
|
|
162
|
+
* accounting for available viewport space.
|
|
163
|
+
* - Ensure that the content can be dismissed using the Esc key.
|
|
164
|
+
*
|
|
165
|
+
* @param {ISelectContentProperties} props - The props for the Select.Content component.
|
|
166
|
+
* @param {boolean} props.raw - Define whether the component is styled or not.
|
|
167
|
+
* @param {boolean} props.defaultOpen - The initial open state of the select. Defaults to false.
|
|
168
|
+
* @param {ReactNode} props.children - The list of Select.Item components to render.
|
|
169
|
+
* @returns {ReactElement} The Select.Content component.
|
|
170
|
+
*/
|
|
171
|
+
const SelectContent = (props: ISelectContentProperties) => {
|
|
172
|
+
const { raw, defaultOpen, children, ...restProps } = props;
|
|
173
|
+
const { id, states, methods } = useSelect();
|
|
174
|
+
const { toggleOpen, setContentProps } = methods;
|
|
175
|
+
|
|
176
|
+
const mounted = React.useRef(false);
|
|
177
|
+
const contentRef = React.useRef<HTMLDivElement>(null);
|
|
178
|
+
|
|
179
|
+
const contentRect = () => contentRef?.current?.getBoundingClientRect();
|
|
180
|
+
const bodyRect = (): DOMRect | undefined => {
|
|
181
|
+
if (typeof document !== "undefined") {
|
|
182
|
+
return document?.body?.getBoundingClientRect();
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const positions = {
|
|
188
|
+
btt: `calc((${states?.triggerProps?.top}px - ${states?.contentProps?.height}px) - (var(--measurement-medium-10) * 2))`,
|
|
189
|
+
ttb: `calc((${states?.triggerProps?.top}px + ${states?.triggerProps?.height}px) + var(--measurement-medium-10))`,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const dimensions = {
|
|
193
|
+
body_height: bodyRect()?.height ?? 0,
|
|
194
|
+
content_height: states.contentProps.height,
|
|
195
|
+
content_bottom: states.contentProps.bottom,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const hasEnoughVerticalSpace =
|
|
199
|
+
dimensions.body_height - dimensions.content_bottom >
|
|
200
|
+
dimensions.content_height - dimensions.content_height * 0.9;
|
|
201
|
+
|
|
202
|
+
React.useEffect(() => {
|
|
203
|
+
if (defaultOpen && toggleOpen) toggleOpen();
|
|
204
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
205
|
+
}, []);
|
|
206
|
+
|
|
207
|
+
React.useEffect(() => {
|
|
208
|
+
mounted.current = true;
|
|
209
|
+
|
|
210
|
+
if (setContentProps) {
|
|
211
|
+
setContentProps({
|
|
212
|
+
top: Number(contentRect()?.top),
|
|
213
|
+
right: Number(contentRect()?.right),
|
|
214
|
+
bottom: Number(contentRect()?.bottom),
|
|
215
|
+
left: Number(contentRect()?.left),
|
|
216
|
+
width: Number(contentRect()?.width),
|
|
217
|
+
height: Number(contentRect()?.height),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return () => {
|
|
222
|
+
mounted.current = false;
|
|
223
|
+
};
|
|
224
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
225
|
+
}, [states.open]);
|
|
226
|
+
|
|
227
|
+
React.useEffect(() => {
|
|
228
|
+
if (!states.open) return;
|
|
229
|
+
|
|
230
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
231
|
+
if (event.key === "Escape" && methods.setOpen) {
|
|
232
|
+
methods.setOpen(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
237
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
238
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
239
|
+
}, [states.open]);
|
|
240
|
+
|
|
241
|
+
if (!states.open) return null;
|
|
242
|
+
return (
|
|
243
|
+
<ScrollArea
|
|
244
|
+
scrollbar
|
|
245
|
+
as={Content}
|
|
246
|
+
ref={contentRef}
|
|
247
|
+
id={id.split("|").at(-1)}
|
|
248
|
+
role="listbox"
|
|
249
|
+
tabIndex={-1}
|
|
250
|
+
aria-labelledby={id.split("|").at(0)}
|
|
251
|
+
data-state={applyDataState(Boolean(states.open))}
|
|
252
|
+
data-side={hasEnoughVerticalSpace ? "bottom" : "top"}
|
|
253
|
+
data-raw={Boolean(raw)}
|
|
254
|
+
style={{
|
|
255
|
+
top: hasEnoughVerticalSpace ? positions.ttb : positions.btt,
|
|
256
|
+
left: `${states?.triggerProps?.left}px`,
|
|
257
|
+
width: `${states?.triggerProps?.width}px`,
|
|
258
|
+
}}
|
|
259
|
+
{...restProps}
|
|
260
|
+
>
|
|
261
|
+
{children}
|
|
262
|
+
</ScrollArea>
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
SelectContent.displayName = "Select.Content";
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Select.Item represents a single option within Select.Content.
|
|
269
|
+
*
|
|
270
|
+
* **Best practices:**
|
|
271
|
+
*
|
|
272
|
+
* - Use a clear and concise label that accurately describes the option.
|
|
273
|
+
* - Ensure that the item can be selected using only the keyboard (Space or Enter).
|
|
274
|
+
* - Use the `disabled` prop to prevent selection of unavailable options.
|
|
275
|
+
* - Provide a meaningful `value` prop that will be stored in the select state upon selection.
|
|
276
|
+
*
|
|
277
|
+
* @param {ISelectItemProperties} props - The props for the Select.Item component.
|
|
278
|
+
* @param {string} props.value - The value associated with this option, stored in the select state on selection.
|
|
279
|
+
* @param {boolean} props.disabled - Whether the item is disabled and cannot be selected. Defaults to false.
|
|
280
|
+
* @param {boolean} props.raw - Define whether the component is styled or not.
|
|
281
|
+
* @param {Function} props.onClick - Optional callback fired when the item is selected via click or keyboard.
|
|
282
|
+
* @param {ReactNode} props.children - The content to be rendered inside the item.
|
|
283
|
+
* @returns {ReactElement} The Select.Item component.
|
|
284
|
+
*/
|
|
285
|
+
const SelectItem = (props: ISelectItemProperties) => {
|
|
286
|
+
const { raw, value, disabled, onClick, children, ...restProps } = props;
|
|
287
|
+
const { states, methods } = useSelect();
|
|
288
|
+
|
|
289
|
+
const isSelected = states?.value === value;
|
|
290
|
+
const handleSelect = (
|
|
291
|
+
event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
|
|
292
|
+
) => {
|
|
293
|
+
if (disabled) return;
|
|
294
|
+
|
|
295
|
+
if (methods.setValue) methods.setValue(value);
|
|
296
|
+
if (methods.setOpen) methods.setOpen(false);
|
|
297
|
+
if (onClick) onClick(event);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLLIElement>) => {
|
|
301
|
+
if (["Space", "Enter"].includes(event.code || event.key) && !disabled) {
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
handleSelect(event);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<List
|
|
309
|
+
role="option"
|
|
310
|
+
tabIndex={0}
|
|
311
|
+
aria-disabled={disabled}
|
|
312
|
+
aria-selected={isSelected}
|
|
313
|
+
data-orientation="vertical"
|
|
314
|
+
data-selected={isSelected}
|
|
315
|
+
data-raw={Boolean(raw)}
|
|
316
|
+
onClick={handleSelect}
|
|
317
|
+
onKeyDown={handleKeyDown}
|
|
318
|
+
{...restProps}
|
|
319
|
+
>
|
|
320
|
+
<Item>{children}</Item>
|
|
321
|
+
</List>
|
|
322
|
+
);
|
|
323
|
+
};
|
|
324
|
+
SelectItem.displayName = "Select.Item";
|
|
325
|
+
|
|
326
|
+
Select.Root = SelectRoot;
|
|
327
|
+
Select.Trigger = SelectTrigger;
|
|
328
|
+
Select.Content = SelectContent;
|
|
329
|
+
Select.Item = SelectItem;
|
|
330
|
+
|
|
331
|
+
export { SelectRoot, Select, SelectTrigger, SelectContent, SelectItem };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// select/styles.ts
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import {
|
|
4
|
+
FieldDefaultStyles,
|
|
5
|
+
FieldVariantsStyles,
|
|
6
|
+
FieldSizeStyles,
|
|
7
|
+
FieldShapeStyles,
|
|
8
|
+
} from "../../field/styles";
|
|
9
|
+
|
|
10
|
+
export const Wrapper = styled.div`
|
|
11
|
+
position: relative;
|
|
12
|
+
width: 100%;
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const Trigger = styled.button<any>`
|
|
16
|
+
all: unset;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
|
|
19
|
+
position: relative;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: space-between;
|
|
23
|
+
gap: var(--measurement-medium-10);
|
|
24
|
+
|
|
25
|
+
${FieldDefaultStyles}
|
|
26
|
+
${FieldVariantsStyles}
|
|
27
|
+
${FieldShapeStyles}
|
|
28
|
+
${FieldSizeStyles}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
cursor: pointer !important;
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
export const Label = styled.span<any>`
|
|
35
|
+
flex: 1;
|
|
36
|
+
text-align: left;
|
|
37
|
+
font-weight: 400;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
text-overflow: ellipsis;
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
export const Content = styled.ul<any>`
|
|
44
|
+
@keyframes select-slide-in-down {
|
|
45
|
+
0% {
|
|
46
|
+
opacity: 0;
|
|
47
|
+
transform: translateY(calc(var(--measurement-small-60) * -1));
|
|
48
|
+
}
|
|
49
|
+
100% {
|
|
50
|
+
opacity: 1;
|
|
51
|
+
transform: translateY(0);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes select-slide-in-up {
|
|
56
|
+
0% {
|
|
57
|
+
opacity: 0;
|
|
58
|
+
transform: translateY(var(--measurement-small-60));
|
|
59
|
+
}
|
|
60
|
+
100% {
|
|
61
|
+
opacity: 1;
|
|
62
|
+
transform: translateY(0);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&[data-raw="false"] {
|
|
67
|
+
position: fixed;
|
|
68
|
+
margin: 0;
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
|
|
71
|
+
padding: var(--measurement-medium-30);
|
|
72
|
+
|
|
73
|
+
list-style: none;
|
|
74
|
+
|
|
75
|
+
background-color: var(--body-color);
|
|
76
|
+
border: var(--measurement-small-10) solid var(--font-color-alpha-10);
|
|
77
|
+
border-radius: var(--measurement-medium-30);
|
|
78
|
+
|
|
79
|
+
z-index: var(--depth-default-100);
|
|
80
|
+
|
|
81
|
+
height: auto;
|
|
82
|
+
max-height: var(--measurement-large-90);
|
|
83
|
+
overflow-y: auto;
|
|
84
|
+
|
|
85
|
+
animation-duration: 0.2s;
|
|
86
|
+
animation-fill-mode: backwards;
|
|
87
|
+
|
|
88
|
+
&[data-side="bottom"] {
|
|
89
|
+
animation-name: select-slide-in-down;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&[data-side="top"] {
|
|
93
|
+
animation-name: select-slide-in-up;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
export const List = styled.li<any>`
|
|
99
|
+
list-style: none;
|
|
100
|
+
padding: 0;
|
|
101
|
+
margin: 0;
|
|
102
|
+
user-select: none;
|
|
103
|
+
|
|
104
|
+
&[data-raw="false"] {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: space-between;
|
|
108
|
+
gap: var(--measurement-medium-10);
|
|
109
|
+
|
|
110
|
+
padding: var(--measurement-medium-30);
|
|
111
|
+
border-radius: var(--measurement-medium-20);
|
|
112
|
+
|
|
113
|
+
text-align: left;
|
|
114
|
+
|
|
115
|
+
color: var(--font-color);
|
|
116
|
+
|
|
117
|
+
outline: none;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
|
|
120
|
+
transition: all ease-in-out 0.2s;
|
|
121
|
+
|
|
122
|
+
&:hover,
|
|
123
|
+
&:focus,
|
|
124
|
+
&:active,
|
|
125
|
+
&:focus-within,
|
|
126
|
+
&:has(:active) {
|
|
127
|
+
background-color: var(--contrast-color);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&[data-selected="true"] {
|
|
131
|
+
color: var(--font-color);
|
|
132
|
+
background-color: var(--contrast-color);
|
|
133
|
+
|
|
134
|
+
&:hover,
|
|
135
|
+
&:focus,
|
|
136
|
+
&:active {
|
|
137
|
+
background-color: var(--font-color-alpha-10);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&[aria-disabled="true"] {
|
|
143
|
+
cursor: not-allowed;
|
|
144
|
+
opacity: 0.6;
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
export const Item = styled.span`
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
gap: var(--measurement-small-60);
|
|
152
|
+
flex: 1;
|
|
153
|
+
white-space: nowrap;
|
|
154
|
+
overflow: hidden;
|
|
155
|
+
text-overflow: ellipsis;
|
|
156
|
+
`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
3
|
|
|
4
|
-
import { Shimmer } from "..";
|
|
4
|
+
import { Page, Shimmer } from "..";
|
|
5
5
|
|
|
6
6
|
const meta = {
|
|
7
7
|
title: "Components/Shimmer",
|
|
@@ -9,9 +9,11 @@ const meta = {
|
|
|
9
9
|
tags: ["autodocs"],
|
|
10
10
|
decorators: [
|
|
11
11
|
(Story) => (
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
12
|
+
<Page>
|
|
13
|
+
<Page.Content className="p-medium-30">
|
|
14
|
+
<Story />
|
|
15
|
+
</Page.Content>
|
|
16
|
+
</Page>
|
|
15
17
|
),
|
|
16
18
|
],
|
|
17
19
|
} satisfies Meta<typeof Shimmer>;
|
package/src/skeleton/index.tsx
CHANGED
|
@@ -4,16 +4,17 @@ import React from "react";
|
|
|
4
4
|
import { SkeletonLoader } from "./styles";
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
ComponentShapeEnum,
|
|
7
8
|
ComponentSizeEnum,
|
|
9
|
+
type IComponentShape,
|
|
8
10
|
type IComponentSize,
|
|
9
|
-
type TComponentShape,
|
|
10
11
|
} from "../../../../types";
|
|
11
12
|
|
|
12
13
|
export interface SkeletonProperties
|
|
13
|
-
extends
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
14
|
+
extends
|
|
15
|
+
IComponentSize,
|
|
16
|
+
IComponentShape,
|
|
17
|
+
React.ComponentPropsWithRef<"span"> {}
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Skeletons are used to convoy a loading state information.
|
|
@@ -26,7 +27,7 @@ export interface SkeletonProperties
|
|
|
26
27
|
export const Skeleton = (props: SkeletonProperties): React.ReactElement => {
|
|
27
28
|
const {
|
|
28
29
|
sizing = ComponentSizeEnum.Medium,
|
|
29
|
-
shape =
|
|
30
|
+
shape = ComponentShapeEnum.Smooth,
|
|
30
31
|
...restProps
|
|
31
32
|
} = props;
|
|
32
33
|
|
|
@@ -8,6 +8,17 @@ import { Page, Spinner } from "..";
|
|
|
8
8
|
const meta = {
|
|
9
9
|
title: "Components/Spinner",
|
|
10
10
|
component: Spinner,
|
|
11
|
+
decorators: [
|
|
12
|
+
(Story) => (
|
|
13
|
+
<Page>
|
|
14
|
+
<Page.Content className="p-medium-30">
|
|
15
|
+
<div className="flex flex-column align-center justify-center h-100 g-medium-30">
|
|
16
|
+
<Story />
|
|
17
|
+
</div>
|
|
18
|
+
</Page.Content>
|
|
19
|
+
</Page>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
11
22
|
tags: ["autodocs"],
|
|
12
23
|
} satisfies Meta<typeof Spinner>;
|
|
13
24
|
export default meta;
|
|
@@ -16,12 +27,26 @@ type Story = StoryObj<typeof meta>;
|
|
|
16
27
|
export const Default: Story = {
|
|
17
28
|
args: {},
|
|
18
29
|
render: ({ ...args }) => (
|
|
19
|
-
<
|
|
20
|
-
<
|
|
30
|
+
<React.Fragment>
|
|
31
|
+
<div className="flex align-center g-medium-30">
|
|
21
32
|
<Spinner sizing="small" {...args} />
|
|
22
33
|
<Spinner sizing="medium" {...args} />
|
|
23
34
|
<Spinner sizing="large" {...args} />
|
|
24
|
-
</
|
|
25
|
-
</
|
|
35
|
+
</div>
|
|
36
|
+
</React.Fragment>
|
|
37
|
+
),
|
|
38
|
+
};
|
|
39
|
+
export const Variants: Story = {
|
|
40
|
+
args: {},
|
|
41
|
+
render: ({ ...args }) => (
|
|
42
|
+
<React.Fragment>
|
|
43
|
+
{["circle", "circle-filled"].map((variant) => (
|
|
44
|
+
<div className="flex align-center g-medium-30" key={variant}>
|
|
45
|
+
<Spinner sizing="small" variant={variant} />
|
|
46
|
+
<Spinner sizing="medium" variant={variant} />
|
|
47
|
+
<Spinner sizing="large" variant={variant} />
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</React.Fragment>
|
|
26
51
|
),
|
|
27
52
|
};
|
package/src/spinner/index.tsx
CHANGED
|
@@ -2,18 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { AnimatedSpinner } from "./styles";
|
|
6
6
|
import type { IComponentSize } from "../../../../types";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
type SpinnerVariant = "circle" | "circle-filled";
|
|
9
|
+
|
|
10
|
+
interface SpinnerProperties extends IComponentSize {
|
|
11
|
+
variant?: SpinnerVariant;
|
|
12
|
+
}
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
|
-
* Spinners are used to
|
|
15
|
+
* Spinners are used to convey a loading state information.
|
|
12
16
|
*
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {
|
|
17
|
+
* @param {SpinnerProperties} props - The props for the Spinner component.
|
|
18
|
+
* @param {string} props.sizing - The size of the component. Defaults to `medium`.
|
|
19
|
+
* @param {SpinnerVariant} props.variant - The spinner animation variant. Defaults to `circle`.
|
|
15
20
|
* @returns {ReactElement} The Spinner component.
|
|
16
21
|
*/
|
|
17
22
|
export const Spinner = (props: SpinnerProperties) => {
|
|
18
|
-
return
|
|
23
|
+
return (
|
|
24
|
+
<AnimatedSpinner
|
|
25
|
+
data-variant={props?.variant ?? "circle"}
|
|
26
|
+
data-size={props?.sizing ?? "medium"}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
19
29
|
};
|