@usefui/components 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.d.mts +246 -3
  3. package/dist/index.d.ts +246 -3
  4. package/dist/index.js +740 -307
  5. package/dist/index.mjs +708 -288
  6. package/package.json +12 -12
  7. package/src/__tests__/MessageBubble.test.tsx +179 -0
  8. package/src/__tests__/Shimmer.test.tsx +122 -0
  9. package/src/__tests__/Tree.test.tsx +275 -0
  10. package/src/accordion/hooks/index.tsx +3 -1
  11. package/src/badge/index.tsx +2 -3
  12. package/src/checkbox/hooks/index.tsx +5 -1
  13. package/src/collapsible/hooks/index.tsx +3 -1
  14. package/src/dialog/hooks/index.tsx +5 -1
  15. package/src/dropdown/hooks/index.tsx +3 -1
  16. package/src/dropdown/index.tsx +9 -9
  17. package/src/field/hooks/index.tsx +5 -1
  18. package/src/field/styles/index.ts +1 -0
  19. package/src/index.ts +6 -0
  20. package/src/message-bubble/MessageBubble.stories.tsx +91 -0
  21. package/src/message-bubble/hooks/index.tsx +41 -0
  22. package/src/message-bubble/index.tsx +153 -0
  23. package/src/message-bubble/styles/index.ts +61 -0
  24. package/src/otp-field/hooks/index.tsx +3 -1
  25. package/src/otp-field/index.tsx +5 -3
  26. package/src/sheet/hooks/index.tsx +5 -1
  27. package/src/shimmer/Shimmer.stories.tsx +95 -0
  28. package/src/shimmer/index.tsx +64 -0
  29. package/src/shimmer/styles/index.ts +33 -0
  30. package/src/switch/hooks/index.tsx +5 -1
  31. package/src/tabs/hooks/index.tsx +5 -1
  32. package/src/text-area/Textarea.stories.tsx +7 -2
  33. package/src/text-area/index.tsx +30 -14
  34. package/src/text-area/styles/index.ts +32 -72
  35. package/src/toolbar/hooks/index.tsx +5 -1
  36. package/src/tooltip/index.tsx +4 -3
  37. package/src/tree/Tree.stories.tsx +139 -0
  38. package/src/tree/hooks/tree-node-provider.tsx +50 -0
  39. package/src/tree/hooks/tree-provider.tsx +75 -0
  40. package/src/tree/index.tsx +231 -0
  41. package/src/tree/styles/index.ts +23 -0
  42. package/tsconfig.build.json +20 -0
  43. package/tsconfig.json +1 -3
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React, { useState, createContext, useContext } from "react";
2
4
  import { IReactChildren, IComponentAPI } from "../../../../../types";
3
5
 
@@ -9,7 +11,9 @@ const defaultComponentAPI = {
9
11
  const DialogContext = createContext<IComponentAPI>(defaultComponentAPI);
10
12
  export const useDialog = () => useContext(DialogContext);
11
13
 
12
- export const DialogProvider = ({ children }: IReactChildren): JSX.Element => {
14
+ export const DialogProvider = ({
15
+ children,
16
+ }: IReactChildren): React.JSX.Element => {
13
17
  const context = useDialogProvider();
14
18
 
15
19
  return (
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React from "react";
2
4
  import { IReactChildren, IComponentAPI } from "../../../../../types";
3
5
 
@@ -22,7 +24,7 @@ export const useDropdownMenu = () => React.useContext(DropdownMenuContext);
22
24
 
23
25
  export const DropdownMenuProvider = ({
24
26
  children,
25
- }: IReactChildren): JSX.Element => {
27
+ }: IReactChildren): React.JSX.Element => {
26
28
  const context = useDropdownMenuProvider();
27
29
 
28
30
  return (
@@ -14,18 +14,15 @@ import {
14
14
  } from "../../../../types";
15
15
 
16
16
  export interface IDropdownContentProperties
17
- extends IComponentStyling,
18
- IComponentSize,
19
- React.ComponentPropsWithRef<"ul"> {
17
+ extends IComponentStyling, IComponentSize, React.ComponentPropsWithRef<"ul"> {
20
18
  defaultOpen?: boolean;
21
19
  }
22
20
  export interface IDropdownItemProperties
23
- extends IComponentStyling,
24
- React.ComponentProps<"li"> {
21
+ extends IComponentStyling, React.ComponentProps<"li"> {
25
22
  radio?: boolean;
26
23
  disabled?: boolean;
27
24
  onClick?: (
28
- event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>
25
+ event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
29
26
  ) => void;
30
27
  onKeyDown?: (event: React.KeyboardEvent<HTMLLIElement>) => void;
31
28
  }
@@ -52,7 +49,7 @@ export interface IDropdownComposition {
52
49
  * @returns {ReactElement} The DropdownMenu component.
53
50
  */
54
51
  const DropdownMenu = ({ children }: React.ComponentProps<"div">) => {
55
- const DropdownContentRef = React.useRef(null);
52
+ const DropdownContentRef = React.useRef<HTMLDivElement | null>(null);
56
53
  const { states, methods } = useDropdownMenu();
57
54
  const { toggleOpen } = methods;
58
55
 
@@ -60,7 +57,10 @@ const DropdownMenu = ({ children }: React.ComponentProps<"div">) => {
60
57
  if (states.open && toggleOpen) toggleOpen();
61
58
  };
62
59
 
63
- useClickOutside(DropdownContentRef, handleClickOutside);
60
+ useClickOutside(
61
+ DropdownContentRef as React.RefObject<HTMLElement>,
62
+ handleClickOutside,
63
+ );
64
64
  useDisabledScroll(Boolean(states.open));
65
65
  return <RootWrapper ref={DropdownContentRef}>{children}</RootWrapper>;
66
66
  };
@@ -266,7 +266,7 @@ const DropdownMenuItem = (props: IDropdownItemProperties) => {
266
266
  click: (
267
267
  event:
268
268
  | React.MouseEvent<HTMLLIElement>
269
- | React.KeyboardEvent<HTMLLIElement>
269
+ | React.KeyboardEvent<HTMLLIElement>,
270
270
  ) => {
271
271
  if (onClick) onClick(event);
272
272
  },
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React, { createContext, useContext } from "react";
2
4
  import { IReactChildren, IComponentAPI } from "../../../../../types";
3
5
 
@@ -9,7 +11,9 @@ const defaultComponentAPI = {
9
11
  const FieldContext = createContext<IComponentAPI>(defaultComponentAPI);
10
12
  export const useField = () => useContext(FieldContext);
11
13
 
12
- export const FieldProvider = ({ children }: IReactChildren): JSX.Element => {
14
+ export const FieldProvider = ({
15
+ children,
16
+ }: IReactChildren): React.JSX.Element => {
13
17
  const context = useFieldProvider();
14
18
 
15
19
  return (
@@ -6,6 +6,7 @@ export const FieldDefaultStyles = css`
6
6
  display: flex;
7
7
  align-items: center;
8
8
  justify-content: center;
9
+ box-sizing: border-box;
9
10
 
10
11
  font-size: var(--fontsize-medium-20);
11
12
 
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ export * from "./dialog";
11
11
  export * from "./divider";
12
12
  export * from "./dropdown";
13
13
  export * from "./field";
14
+ export * from "./message-bubble";
14
15
  export * from "./otp-field";
15
16
  export * from "./overlay";
16
17
  export * from "./page";
@@ -18,6 +19,7 @@ export * from "./portal";
18
19
  export * from "./privacy-field";
19
20
  export * from "./resizable";
20
21
  export * from "./sheet";
22
+ export * from "./shimmer";
21
23
  export * from "./scrollarea";
22
24
  export * from "./spinner";
23
25
  export * from "./skeleton";
@@ -28,6 +30,7 @@ export * from "./text-area";
28
30
  export * from "./toggle";
29
31
  export * from "./toolbar";
30
32
  export * from "./tooltip";
33
+ export * from "./tree";
31
34
 
32
35
  export { useAccordion } from "./accordion/hooks";
33
36
  export { useCheckbox } from "./checkbox/hooks";
@@ -39,3 +42,6 @@ export { useSheet } from "./sheet/hooks";
39
42
  export { useSwitch } from "./switch/hooks";
40
43
  export { useTabs } from "./tabs/hooks";
41
44
  export { useToolbar } from "./toolbar/hooks";
45
+ export { useMessageBubble } from "./message-bubble/hooks";
46
+ export { useTree } from "./tree/hooks/tree-provider";
47
+ export { useTreeNode } from "./tree/hooks/tree-node-provider";
@@ -0,0 +1,91 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+
4
+ import { MessageBubble } from "..";
5
+
6
+ const meta = {
7
+ title: "Components/MessageBubble",
8
+ component: MessageBubble,
9
+ tags: ["autodocs"],
10
+ decorators: [
11
+ (Story) => (
12
+ <div className="m-medium-30">
13
+ <Story />
14
+ </div>
15
+ ),
16
+ ],
17
+ } satisfies Meta<typeof MessageBubble>;
18
+ export default meta;
19
+
20
+ type Story = StoryObj<typeof meta>;
21
+
22
+ const MOCK_DATE = new Date("2026-03-17T13:00:00Z");
23
+ const MOCK_MESSAGE = "Hey, how are you doing?";
24
+
25
+ export const Default: Story = {
26
+ args: {
27
+ side: "left",
28
+ raw: false,
29
+ },
30
+ argTypes: {
31
+ side: {
32
+ options: ["left", "right"],
33
+ control: { type: "radio" },
34
+ },
35
+ raw: {
36
+ control: { type: "boolean" },
37
+ },
38
+ },
39
+ render: ({ ...args }) => (
40
+ <MessageBubble.Root>
41
+ <MessageBubble {...args}>
42
+ <MessageBubble.Content>{MOCK_MESSAGE}</MessageBubble.Content>
43
+ <MessageBubble.Meta createdAt={MOCK_DATE} />
44
+ </MessageBubble>
45
+ </MessageBubble.Root>
46
+ ),
47
+ };
48
+
49
+ export const Left: Story = {
50
+ render: () => (
51
+ <MessageBubble.Root>
52
+ <MessageBubble side="left">
53
+ <MessageBubble.Content>{MOCK_MESSAGE}</MessageBubble.Content>
54
+ <MessageBubble.Meta createdAt={MOCK_DATE} />
55
+ </MessageBubble>
56
+ </MessageBubble.Root>
57
+ ),
58
+ };
59
+
60
+ export const Right: Story = {
61
+ render: () => (
62
+ <MessageBubble.Root>
63
+ <MessageBubble side="right">
64
+ <MessageBubble.Content>{MOCK_MESSAGE}</MessageBubble.Content>
65
+ <MessageBubble.Meta createdAt={MOCK_DATE} />
66
+ </MessageBubble>
67
+ </MessageBubble.Root>
68
+ ),
69
+ };
70
+
71
+ export const Conversation: Story = {
72
+ render: () => (
73
+ <React.Fragment>
74
+ {(
75
+ [
76
+ { side: "left", message: "Hey, how are you doing?" },
77
+ { side: "right", message: "All good! What about you?" },
78
+ { side: "left", message: "Pretty great, thanks for asking 🐻" },
79
+ { side: "right", message: "Glad to hear it! 🐻❄️" },
80
+ ] as const
81
+ ).map(({ side, message }, index) => (
82
+ <MessageBubble.Root key={index}>
83
+ <MessageBubble side={side}>
84
+ <MessageBubble.Content>{message}</MessageBubble.Content>
85
+ <MessageBubble.Meta createdAt={MOCK_DATE} />
86
+ </MessageBubble>
87
+ </MessageBubble.Root>
88
+ ))}
89
+ </React.Fragment>
90
+ ),
91
+ };
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ import React, { useState, createContext, useContext } from "react";
4
+
5
+ import { IReactChildren, IComponentAPI } from "../../../../../types";
6
+ import { MessageBubbleSide } from "../";
7
+
8
+ const defaultComponentAPI = {
9
+ id: "",
10
+ states: {},
11
+ methods: {},
12
+ };
13
+ const MessageBubbleContext = createContext<IComponentAPI>(defaultComponentAPI);
14
+ export const useMessageBubble = () => useContext(MessageBubbleContext);
15
+
16
+ export const MessageBubbleProvider = ({
17
+ children,
18
+ }: IReactChildren): React.JSX.Element => {
19
+ const context = useMessageBubbleProvider();
20
+
21
+ return (
22
+ <MessageBubbleContext.Provider value={context}>
23
+ {children}
24
+ </MessageBubbleContext.Provider>
25
+ );
26
+ };
27
+
28
+ function useMessageBubbleProvider(): IComponentAPI {
29
+ const [side, setSide] = useState<MessageBubbleSide | null>(null);
30
+ const MessageBubbleId = React.useId();
31
+
32
+ return {
33
+ id: MessageBubbleId,
34
+ states: {
35
+ side,
36
+ },
37
+ methods: {
38
+ applySide: (side: MessageBubbleSide): string | void => setSide(side),
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,153 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useMessageBubble, MessageBubbleProvider } from "./hooks";
5
+
6
+ import {
7
+ MessageBubbleWrapper,
8
+ MessageBubbleBadge,
9
+ MessageBubbleContentWrapper,
10
+ MessageBubbleMetaWrapper,
11
+ } from "./styles";
12
+
13
+ import { IComponentStyling, IReactChildren } from "../../../../types";
14
+
15
+ export type MessageBubbleSide = "left" | "right";
16
+
17
+ export interface IMessageBubbleContext {
18
+ side: MessageBubbleSide;
19
+ }
20
+
21
+ export interface IMessageBubbleProperties
22
+ extends IComponentStyling, React.HTMLAttributes<HTMLDivElement> {
23
+ side: MessageBubbleSide;
24
+ }
25
+
26
+ export interface IMessageBubbleContentProperties
27
+ extends IComponentStyling, React.HTMLAttributes<HTMLDivElement> {
28
+ children: string;
29
+ }
30
+
31
+ export interface IMessageBubbleMetaProperties
32
+ extends IComponentStyling, React.HTMLAttributes<HTMLDivElement> {
33
+ createdAt: Date;
34
+ }
35
+
36
+ const MessageBubbleRoot = ({ children }: IReactChildren) => {
37
+ return <MessageBubbleProvider>{children}</MessageBubbleProvider>;
38
+ };
39
+ MessageBubbleRoot.displayName = "MessageBubble.Root";
40
+
41
+ /**
42
+ * MessageBubble is used to display a chat message with an optional side and raw layout.
43
+ *
44
+ * **Best practices:**
45
+ *
46
+ * - Always wrap MessageBubble inside a MessageBubble.Root to provide the necessary context.
47
+ * - Use `side` to visually distinguish between sent and received messages.
48
+ * - Pair with MessageBubble.Content and MessageBubble.Meta for a complete message layout.
49
+ *
50
+ * @param {IMessageBubbleProperties} props - The props for the MessageBubble component.
51
+ * @param {"left" | "right"} props.side - The side the bubble is aligned to. Propagated to all child compounds via context.
52
+ * @param {boolean} props.raw - When true, removes default styling for custom layouts.
53
+ * @param {ReactNode} props.children - The content to be rendered inside the bubble.
54
+ * @returns {ReactElement} The MessageBubble component.
55
+ */
56
+ const MessageBubble = (props: IMessageBubbleProperties) => {
57
+ const { side, raw, children, ...restProps } = props;
58
+ const { methods } = useMessageBubble();
59
+
60
+ React.useEffect(() => {
61
+ if (side && methods?.applySide) methods.applySide(side);
62
+ }, [side]);
63
+
64
+ return (
65
+ <MessageBubbleWrapper
66
+ data-raw={Boolean(raw)}
67
+ data-side={side}
68
+ aria-label={restProps["aria-label"] ?? `message-bubble-${side}`}
69
+ {...restProps}
70
+ >
71
+ {children}
72
+ </MessageBubbleWrapper>
73
+ );
74
+ };
75
+ MessageBubble.displayName = "MessageBubble";
76
+
77
+ /**
78
+ * MessageBubble.Content is used to display the text or rich content of the message.
79
+ *
80
+ * **Best practices:**
81
+ *
82
+ * - Place MessageBubble.Content inside a MessageBubble to inherit the correct side context.
83
+ * - Avoid nesting interactive elements inside the content that may conflict with bubble focus management.
84
+ *
85
+ * @param {IMessageBubbleContentProperties} props - The props for the MessageBubble.Content component.
86
+ * @param {boolean} props.raw - When true, removes default styling for custom layouts.
87
+ * @param {ReactNode} props.children - The message text or rich content to render.
88
+ * @returns {ReactElement} The MessageBubble.Content component.
89
+ */
90
+ const MessageBubbleContent = (props: IMessageBubbleContentProperties) => {
91
+ const { children, raw, ...restProps } = props;
92
+ const { id, states } = useMessageBubble();
93
+
94
+ return (
95
+ <MessageBubbleBadge
96
+ variant="secondary"
97
+ data-raw={Boolean(raw)}
98
+ data-side={states?.side}
99
+ aria-label={`message-bubble-content-${id}`}
100
+ {...restProps}
101
+ >
102
+ <MessageBubbleContentWrapper>{children}</MessageBubbleContentWrapper>
103
+ </MessageBubbleBadge>
104
+ );
105
+ };
106
+ MessageBubbleContent.displayName = "MessageBubble.Content";
107
+
108
+ /**
109
+ * MessageBubble.Meta is used to display metadata associated with the message, such as its timestamp.
110
+ *
111
+ * **Best practices:**
112
+ *
113
+ * - Always provide a valid `createdAt` date for accurate timestamp display.
114
+ * - Place MessageBubble.Meta after MessageBubble.Content for a natural reading flow.
115
+ * - The side is automatically inherited from context — do not pass it manually.
116
+ *
117
+ * @param {IMessageBubbleMetaProperties} props - The props for the MessageBubble.Meta component.
118
+ * @param {Date} props.createdAt - The date the message was created. Formatted as medium date and short time.
119
+ * @param {boolean} props.raw - When true, removes default styling for custom layouts.
120
+ * @returns {ReactElement} The MessageBubble.Meta component.
121
+ */
122
+ const MessageBubbleMeta = (props: IMessageBubbleMetaProperties) => {
123
+ const { createdAt, raw, ...restProps } = props;
124
+ const { states } = useMessageBubble();
125
+
126
+ const formattedDate = new Intl.DateTimeFormat("en-US", {
127
+ dateStyle: "medium",
128
+ timeStyle: "short",
129
+ }).format(createdAt);
130
+
131
+ return (
132
+ <MessageBubbleMetaWrapper
133
+ data-raw={Boolean(raw)}
134
+ data-side={states?.side}
135
+ aria-label={`message-bubble-meta-${states?.side}`}
136
+ {...restProps}
137
+ >
138
+ {formattedDate}
139
+ </MessageBubbleMetaWrapper>
140
+ );
141
+ };
142
+ MessageBubbleMeta.displayName = "MessageBubble.Meta";
143
+
144
+ MessageBubble.Root = MessageBubbleRoot;
145
+ MessageBubble.Content = MessageBubbleContent;
146
+ MessageBubble.Meta = MessageBubbleMeta;
147
+
148
+ export {
149
+ MessageBubble,
150
+ MessageBubbleRoot,
151
+ MessageBubbleContent,
152
+ MessageBubbleMeta,
153
+ };
@@ -0,0 +1,61 @@
1
+ import styled from "styled-components";
2
+ import { Badge, IBadgeProperties } from "../../";
3
+
4
+ export const MessageBubbleWrapper = styled.div`
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--measurement-medium-10);
8
+
9
+ &[data-side="right"] {
10
+ align-items: flex-end;
11
+ }
12
+
13
+ &[data-side="left"] {
14
+ align-items: flex-start;
15
+ }
16
+ `;
17
+
18
+ export const MessageBubbleBadge: React.FC<IBadgeProperties> = styled(Badge)`
19
+ position: relative;
20
+ max-width: var(--measurement-large-90);
21
+ width: 100%;
22
+ justify-self: flex-end;
23
+ padding: var(--measurement-medium-30) var(--measurement-medium-50) !important;
24
+ border-radius: var(--measurement-medium-60) !important;
25
+
26
+ &[data-side="left"] {
27
+ background-color: var(--contrast-color) !important;
28
+ border-bottom-left-radius: 0 !important;
29
+ }
30
+
31
+ &[data-side="right"] {
32
+ background-color: var(--font-color-alpha-10) !important;
33
+ border-bottom-right-radius: 0 !important;
34
+ }
35
+ `;
36
+
37
+ export const MessageBubbleContentWrapper = styled.div`
38
+ line-height: 1.5;
39
+ font-weight: 500;
40
+ word-break: keep-all;
41
+ width: 100%;
42
+
43
+ * {
44
+ font-size: var(--fontsize-medium-10) !important;
45
+ }
46
+ `;
47
+
48
+ export const MessageBubbleMetaWrapper = styled.div`
49
+ display: flex;
50
+ align-items: center;
51
+ gap: var(--measurement-medium-10);
52
+ width: 100%;
53
+
54
+ &[data-side="right"] {
55
+ justify-content: flex-end;
56
+ }
57
+
58
+ &[data-side="left"] {
59
+ justify-content: flex-start;
60
+ }
61
+ `;
@@ -1,9 +1,11 @@
1
+ "use client";
2
+
1
3
  import React from "react";
2
4
 
3
5
  import type { OTPFieldContextType } from "../types";
4
6
 
5
7
  export const OTPFieldContext = React.createContext<OTPFieldContextType | null>(
6
- null
8
+ null,
7
9
  );
8
10
  export const useOTPField = () => {
9
11
  const context = React.useContext(OTPFieldContext);
@@ -26,7 +26,7 @@ const OTPField = ({ children, length, onComplete }: OTPFieldProps) => {
26
26
 
27
27
  const inputRefs = React.useRef<(HTMLInputElement | null)[]>([]);
28
28
  const [otp, setOtp] = React.useState<string[]>(
29
- Array.from({ length: defaultLength }, () => "")
29
+ Array.from({ length: defaultLength }, () => ""),
30
30
  );
31
31
  const [activeIndex, setActiveIndex] = React.useState<number>(0);
32
32
 
@@ -121,7 +121,7 @@ const OTPField = ({ children, length, onComplete }: OTPFieldProps) => {
121
121
  */
122
122
  const timeout = setTimeout(
123
123
  () => inputRefs.current[targetIndex]?.select(),
124
- 0
124
+ 0,
125
125
  );
126
126
 
127
127
  return () => clearTimeout(timeout);
@@ -209,7 +209,9 @@ const OTPFieldSlot = ({
209
209
 
210
210
  return (
211
211
  <OTPCell
212
- ref={(el) => (inputRefs.current[index] = el)}
212
+ ref={(el) => {
213
+ inputRefs.current[index] = el;
214
+ }}
213
215
  type="text"
214
216
  data-testid="otp-field-slot"
215
217
  data-active={activeIndex === index}
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React from "react";
2
4
  import { IReactChildren } from "../../../../../types";
3
5
 
@@ -15,7 +17,9 @@ const SheetContext = React.createContext<IContextProperties>({
15
17
  });
16
18
  export const useSheet = () => React.useContext(SheetContext);
17
19
 
18
- export const SheetProvider = ({ children }: IReactChildren): JSX.Element => {
20
+ export const SheetProvider = ({
21
+ children,
22
+ }: IReactChildren): React.JSX.Element => {
19
23
  const context = useSheetProvider();
20
24
 
21
25
  return (
@@ -0,0 +1,95 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+
4
+ import { Shimmer } from "..";
5
+
6
+ const meta = {
7
+ title: "Components/Shimmer",
8
+ component: Shimmer,
9
+ tags: ["autodocs"],
10
+ decorators: [
11
+ (Story) => (
12
+ <div className="m-medium-30">
13
+ <Story />
14
+ </div>
15
+ ),
16
+ ],
17
+ } satisfies Meta<typeof Shimmer>;
18
+ export default meta;
19
+
20
+ type Story = StoryObj<typeof meta>;
21
+
22
+ export const Default: Story = {
23
+ args: {
24
+ children: "Loading your content…",
25
+ duration: 2,
26
+ spread: 200,
27
+ shimmerColor: "var(--font-color-alpha-60)",
28
+ baseColor: "var(--font-color-alpha-30)",
29
+ },
30
+ argTypes: {
31
+ duration: {
32
+ control: { type: "number", min: 0.5, max: 10, step: 0.5 },
33
+ },
34
+ spread: {
35
+ control: { type: "number", min: 100, max: 500, step: 50 },
36
+ },
37
+ shimmerColor: {
38
+ control: { type: "color" },
39
+ },
40
+ baseColor: {
41
+ control: { type: "color" },
42
+ },
43
+ },
44
+ render: ({ ...args }) => <Shimmer {...args} />,
45
+ };
46
+
47
+ export const SlowAnimation: Story = {
48
+ render: ({ ...args }) => (
49
+ <Shimmer duration={6} spread={200}>
50
+ Slowly shimmering text…
51
+ </Shimmer>
52
+ ),
53
+ };
54
+
55
+ export const FastAnimation: Story = {
56
+ render: ({ ...args }) => (
57
+ <Shimmer duration={0.8} spread={200}>
58
+ Rapidly shimmering text…
59
+ </Shimmer>
60
+ ),
61
+ };
62
+
63
+ export const WideSpread: Story = {
64
+ render: ({ ...args }) => (
65
+ <Shimmer spread={400}>Wide spread shimmer effect</Shimmer>
66
+ ),
67
+ };
68
+
69
+ export const CustomColors: Story = {
70
+ render: ({ ...args }) => (
71
+ <Shimmer
72
+ shimmerColor="var(--alpha-blue-80)"
73
+ baseColor="var(--alpha-blue-30)"
74
+ >
75
+ Custom branded shimmer
76
+ </Shimmer>
77
+ ),
78
+ };
79
+
80
+ export const Group: Story = {
81
+ render: ({ ...args }) => (
82
+ <React.Fragment>
83
+ {[
84
+ "Fetching user profile…",
85
+ "Loading dashboard…",
86
+ "Syncing your data…",
87
+ "Preparing your workspace…",
88
+ ].map((label) => (
89
+ <div key={label} className="m-b-medium-30">
90
+ <Shimmer>{label}</Shimmer>
91
+ </div>
92
+ ))}
93
+ </React.Fragment>
94
+ ),
95
+ };