@usefui/components 1.5.3 → 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 (38) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.d.mts +237 -1
  3. package/dist/index.d.ts +237 -1
  4. package/dist/index.js +702 -231
  5. package/dist/index.mjs +668 -210
  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/index.ts +6 -0
  19. package/src/message-bubble/MessageBubble.stories.tsx +91 -0
  20. package/src/message-bubble/hooks/index.tsx +41 -0
  21. package/src/message-bubble/index.tsx +153 -0
  22. package/src/message-bubble/styles/index.ts +61 -0
  23. package/src/otp-field/hooks/index.tsx +3 -1
  24. package/src/otp-field/index.tsx +5 -3
  25. package/src/sheet/hooks/index.tsx +5 -1
  26. package/src/shimmer/Shimmer.stories.tsx +95 -0
  27. package/src/shimmer/index.tsx +64 -0
  28. package/src/shimmer/styles/index.ts +33 -0
  29. package/src/switch/hooks/index.tsx +5 -1
  30. package/src/tabs/hooks/index.tsx +5 -1
  31. package/src/toolbar/hooks/index.tsx +5 -1
  32. package/src/tree/Tree.stories.tsx +139 -0
  33. package/src/tree/hooks/tree-node-provider.tsx +50 -0
  34. package/src/tree/hooks/tree-provider.tsx +75 -0
  35. package/src/tree/index.tsx +231 -0
  36. package/src/tree/styles/index.ts +23 -0
  37. package/tsconfig.build.json +20 -0
  38. package/tsconfig.json +1 -3
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { TextShimmerWrapper } from "./styles";
5
+ import { IComponentStyling } from "../../../../types";
6
+
7
+ const DEFAULT_DURATION = 2;
8
+ const DEFAULT_SPREAD = 200;
9
+ const DEFAULT_SHIMMER_COLOR = "var(--font-color-alpha-60)";
10
+ const DEFAULT_BASE_COLOR = "var(--font-color-alpha-30)";
11
+
12
+ export interface ITextShimmerProperties
13
+ extends IComponentStyling, React.HTMLAttributes<HTMLSpanElement> {
14
+ duration?: number;
15
+ spread?: number;
16
+ shimmerColor?: string;
17
+ baseColor?: string;
18
+ }
19
+
20
+ /**
21
+ * Shimmer applies an animated shimmer gradient effect to inline text content.
22
+ *
23
+ * **Best practices:**
24
+ *
25
+ * - Use to indicate loading states for text content.
26
+ * - Prefer CSS custom properties for `shimmerColor` and `baseColor` to stay consistent with your design tokens.
27
+ * - Avoid using on large blocks of text; favour short labels or headings.
28
+ *
29
+ * @param {ITextShimmerProperties} props - The props for the Shimmer component.
30
+ * @param {ReactNode} props.children - The text content to apply the shimmer effect to.
31
+ * @param {boolean} props.raw - Whether the component is unstyled.
32
+ * @param {number} props.duration - Animation cycle duration in seconds. Defaults to 2.
33
+ * @param {number} props.spread - Gradient spread width as a percentage. Defaults to 200.
34
+ * @param {string} props.shimmerColor - Highlight color of the shimmer. Defaults to `--font-color-alpha-60`.
35
+ * @param {string} props.baseColor - Base text gradient color. Defaults to `--font-color-alpha-30`.
36
+ * @returns {ReactElement} The Shimmer component.
37
+ */
38
+ export const Shimmer = (props: ITextShimmerProperties) => {
39
+ const {
40
+ children,
41
+ raw,
42
+ duration = DEFAULT_DURATION,
43
+ spread = DEFAULT_SPREAD,
44
+ shimmerColor = DEFAULT_SHIMMER_COLOR,
45
+ baseColor = DEFAULT_BASE_COLOR,
46
+ ...restProps
47
+ } = props;
48
+
49
+ return (
50
+ <TextShimmerWrapper
51
+ data-raw={Boolean(raw)}
52
+ data-duration={duration}
53
+ data-spread={spread}
54
+ data-shimmer-color={shimmerColor}
55
+ data-base-color={baseColor}
56
+ aria-label={restProps["aria-label"] ?? "shimmer-text"}
57
+ {...restProps}
58
+ >
59
+ {children}
60
+ </TextShimmerWrapper>
61
+ );
62
+ };
63
+
64
+ Shimmer.displayName = "Shimmer";
@@ -0,0 +1,33 @@
1
+ import styled, { keyframes } from "styled-components";
2
+
3
+ const shimmer = keyframes`
4
+ 0% {
5
+ background-position: 200% center;
6
+ }
7
+ 100% {
8
+ background-position: -200% center;
9
+ }
10
+ `;
11
+
12
+ export const TextShimmerWrapper = styled.span<{
13
+ "data-duration": number;
14
+ "data-spread": number;
15
+ "data-shimmer-color": string;
16
+ "data-base-color": string;
17
+ }>`
18
+ background: linear-gradient(
19
+ 90deg,
20
+ ${({ "data-base-color": baseColor }) => baseColor} 0%,
21
+ ${({ "data-shimmer-color": shimmerColor }) => shimmerColor} 40%,
22
+ ${({ "data-base-color": baseColor }) => baseColor} 60%,
23
+ ${({ "data-base-color": baseColor }) => baseColor} 100%
24
+ );
25
+ background-size: ${({ "data-spread": spread }) => spread}% auto;
26
+ background-clip: text;
27
+ -webkit-background-clip: text;
28
+ -webkit-text-fill-color: transparent;
29
+ color: transparent;
30
+ animation: ${shimmer} ${({ "data-duration": duration }) => duration}s linear
31
+ infinite;
32
+ display: inline-block;
33
+ `;
@@ -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 SwitchContext = createContext<IComponentAPI>(defaultComponentAPI);
10
12
  export const useSwitch = () => useContext(SwitchContext);
11
13
 
12
- export const SwitchProvider = ({ children }: IReactChildren): JSX.Element => {
14
+ export const SwitchProvider = ({
15
+ children,
16
+ }: IReactChildren): React.JSX.Element => {
13
17
  const context = useSwitchProvider();
14
18
 
15
19
  return (
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React, { createContext, useContext, useState } from "react";
2
4
  import { IReactChildren, IComponentAPI } from "../../../../../types";
3
5
 
@@ -9,7 +11,9 @@ const defaultComponentAPI = {
9
11
  const TabsContext = createContext<IComponentAPI>(defaultComponentAPI);
10
12
  export const useTabs = () => useContext(TabsContext);
11
13
 
12
- export const TabsProvider = ({ children }: IReactChildren): JSX.Element => {
14
+ export const TabsProvider = ({
15
+ children,
16
+ }: IReactChildren): React.JSX.Element => {
13
17
  const context = useTabsProvider();
14
18
 
15
19
  return (
@@ -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 ToolbarContext = createContext<IComponentAPI>(defaultComponentAPI);
10
12
  export const useToolbar = () => useContext(ToolbarContext);
11
13
 
12
- export const ToolbarProvider = ({ children }: IReactChildren): JSX.Element => {
14
+ export const ToolbarProvider = ({
15
+ children,
16
+ }: IReactChildren): React.JSX.Element => {
13
17
  const context = useToolbarProvider();
14
18
 
15
19
  return (
@@ -0,0 +1,139 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+
4
+ import { Tree } from "..";
5
+ import { ComponentSizeEnum, ComponentVariantEnum } from "../../../../types";
6
+
7
+ const meta = {
8
+ title: "Components/Tree",
9
+ component: Tree,
10
+ tags: ["autodocs"],
11
+ decorators: [
12
+ (Story) => (
13
+ <div className="m-medium-30">
14
+ <Story />
15
+ </div>
16
+ ),
17
+ ],
18
+ } satisfies Meta<typeof Tree>;
19
+ export default meta;
20
+
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ export const Default: Story = {
24
+ argTypes: {
25
+ spacing: {
26
+ options: [
27
+ ComponentSizeEnum.Small,
28
+ ComponentSizeEnum.Medium,
29
+ ComponentSizeEnum.Large,
30
+ ],
31
+ control: { type: "radio" },
32
+ },
33
+ variant: {
34
+ options: [
35
+ ComponentVariantEnum.Primary,
36
+ ComponentVariantEnum.Secondary,
37
+ ComponentVariantEnum.Tertiary,
38
+ ComponentVariantEnum.Mono,
39
+ ComponentVariantEnum.Border,
40
+ ComponentVariantEnum.Ghost,
41
+ ],
42
+ control: { type: "radio" },
43
+ },
44
+ sizing: {
45
+ options: [
46
+ ComponentSizeEnum.Small,
47
+ ComponentSizeEnum.Medium,
48
+ ComponentSizeEnum.Large,
49
+ ],
50
+ control: { type: "radio" },
51
+ },
52
+ },
53
+ render: ({ ...args }) => (
54
+ <Tree.Root>
55
+ <Tree>
56
+ <Tree.Node nodeId="src">
57
+ <Tree.Trigger nodeId="src">src</Tree.Trigger>
58
+ <Tree.Content nodeId="src">
59
+ <Tree.Node level={1} nodeId="index.ts">
60
+ <Tree.Trigger nodeId="index.ts">index.ts</Tree.Trigger>
61
+ </Tree.Node>
62
+ </Tree.Content>
63
+ </Tree.Node>
64
+ </Tree>
65
+ </Tree.Root>
66
+ ),
67
+ };
68
+
69
+ export const DefaultOpen: Story = {
70
+ render: ({ ...args }) => (
71
+ <Tree.Root>
72
+ <Tree>
73
+ <Tree.Node nodeId="src">
74
+ <Tree.Trigger nodeId="src">src</Tree.Trigger>
75
+ <Tree.Content nodeId="src" defaultOpen>
76
+ <Tree.Node level={1} nodeId="index.ts">
77
+ <Tree.Trigger nodeId="index.ts">index.ts</Tree.Trigger>
78
+ </Tree.Node>
79
+ </Tree.Content>
80
+ </Tree.Node>
81
+ </Tree>
82
+ </Tree.Root>
83
+ ),
84
+ };
85
+
86
+ export const Nested: Story = {
87
+ render: ({ ...args }) => (
88
+ <Tree.Root>
89
+ <Tree>
90
+ <Tree.Node nodeId="src">
91
+ <Tree.Trigger nodeId="src">src</Tree.Trigger>
92
+ <Tree.Content nodeId="src" defaultOpen>
93
+ <Tree.Node level={1} nodeId="components">
94
+ <Tree.Trigger nodeId="components">components</Tree.Trigger>
95
+ <Tree.Content nodeId="components" defaultOpen>
96
+ <Tree.Node level={2} nodeId="button.ts">
97
+ <Tree.Trigger nodeId="button.ts">button.ts</Tree.Trigger>
98
+ </Tree.Node>
99
+ <Tree.Node level={2} nodeId="card.ts" isLast>
100
+ <Tree.Trigger nodeId="card.ts">card.ts</Tree.Trigger>
101
+ </Tree.Node>
102
+ </Tree.Content>
103
+ </Tree.Node>
104
+ <Tree.Node level={1} nodeId="index.ts" isLast>
105
+ <Tree.Trigger nodeId="index.ts">index.ts</Tree.Trigger>
106
+ </Tree.Node>
107
+ </Tree.Content>
108
+ </Tree.Node>
109
+ </Tree>
110
+ </Tree.Root>
111
+ ),
112
+ };
113
+
114
+ export const Group: Story = {
115
+ render: ({ ...args }) => (
116
+ <Tree.Root>
117
+ <Tree>
118
+ {["src", "public", "tests", "docs", "config"].map(
119
+ (item, index, array) => (
120
+ <Tree.Node
121
+ key={item}
122
+ nodeId={item}
123
+ isLast={index === array.length - 1}
124
+ >
125
+ <Tree.Trigger nodeId={item}>{item}</Tree.Trigger>
126
+ <Tree.Content nodeId={item}>
127
+ <Tree.Node level={1} nodeId={`${item}/index.ts`} isLast>
128
+ <Tree.Trigger nodeId={`${item}/index.ts`}>
129
+ index.ts
130
+ </Tree.Trigger>
131
+ </Tree.Node>
132
+ </Tree.Content>
133
+ </Tree.Node>
134
+ ),
135
+ )}
136
+ </Tree>
137
+ </Tree.Root>
138
+ ),
139
+ };
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import React, { createContext, useContext } from "react";
4
+ import { IReactChildren, IComponentAPI } from "../../../../../types";
5
+
6
+ const defaultTreeNodeAPI: IComponentAPI = {
7
+ id: "",
8
+ states: {},
9
+ methods: {},
10
+ };
11
+
12
+ const TreeNodeContext = createContext<IComponentAPI>(defaultTreeNodeAPI);
13
+ export const useTreeNode = () => useContext(TreeNodeContext);
14
+
15
+ export interface ITreeNodeProviderProperties extends IReactChildren {
16
+ nodeId: string;
17
+ level: number;
18
+ isLast: boolean;
19
+ }
20
+
21
+ export const TreeNodeProvider = ({
22
+ children,
23
+ nodeId,
24
+ level,
25
+ isLast,
26
+ }: ITreeNodeProviderProperties): React.JSX.Element => {
27
+ const context = useTreeNodeProviderContext({ nodeId, level, isLast });
28
+
29
+ return (
30
+ <TreeNodeContext.Provider value={context}>
31
+ {children}
32
+ </TreeNodeContext.Provider>
33
+ );
34
+ };
35
+
36
+ function useTreeNodeProviderContext({
37
+ nodeId,
38
+ level,
39
+ isLast,
40
+ }: Omit<ITreeNodeProviderProperties, "children">): IComponentAPI {
41
+ return {
42
+ id: nodeId,
43
+ states: {
44
+ nodeId,
45
+ level,
46
+ isLast,
47
+ },
48
+ methods: {},
49
+ };
50
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import React, { useState, createContext, useContext } from "react";
4
+ import { IReactChildren, IComponentAPI } from "../../../../../types";
5
+
6
+ // ─── Tree Context ─────────────────────────────────────────────────────────────
7
+
8
+ const defaultTreeAPI: IComponentAPI = {
9
+ id: "",
10
+ states: {},
11
+ methods: {},
12
+ };
13
+
14
+ const TreeContext = createContext<IComponentAPI>(defaultTreeAPI);
15
+ export const useTree = () => useContext(TreeContext);
16
+
17
+ export interface ITreeProviderProperties extends IReactChildren {
18
+ defaultExpandedIds?: string[];
19
+ onSelectionChange?: (ids: string[]) => void;
20
+ }
21
+
22
+ export const TreeProvider = ({
23
+ children,
24
+ defaultExpandedIds = [],
25
+ onSelectionChange,
26
+ }: ITreeProviderProperties): React.JSX.Element => {
27
+ const context = useTreeProviderContext({
28
+ defaultExpandedIds,
29
+ onSelectionChange,
30
+ });
31
+
32
+ return (
33
+ <TreeContext.Provider value={context}>{children}</TreeContext.Provider>
34
+ );
35
+ };
36
+
37
+ function useTreeProviderContext({
38
+ defaultExpandedIds,
39
+ onSelectionChange,
40
+ }: Omit<ITreeProviderProperties, "children">): IComponentAPI {
41
+ const treeId = React.useId();
42
+ const [expandedIds, setExpandedIds] = useState<Set<string>>(
43
+ () => new Set(defaultExpandedIds),
44
+ );
45
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());
46
+
47
+ return {
48
+ id: treeId,
49
+ states: {
50
+ expandedIds,
51
+ selectedIds,
52
+ },
53
+ methods: {
54
+ isExpanded: (id: string): boolean => expandedIds.has(id),
55
+ isSelected: (id: string): boolean => selectedIds.has(id),
56
+ toggleExpanded: (id: string): void => {
57
+ setExpandedIds((prev) => {
58
+ const next = new Set(prev);
59
+ next.has(id) ? next.delete(id) : next.add(id);
60
+ return next;
61
+ });
62
+ },
63
+ toggleSelected: (id: string): void => {
64
+ setSelectedIds((prev) => {
65
+ const next = new Set(prev);
66
+ next.has(id) ? next.delete(id) : next.add(id);
67
+ onSelectionChange?.(Array.from(next));
68
+ return next;
69
+ });
70
+ },
71
+ getTreeId: ({ nodeId, type }: Record<string, string>): string =>
72
+ `${treeId}-${type}-${nodeId}`,
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,231 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+
5
+ import { TreeProvider, useTree } from "./hooks/tree-provider";
6
+ import { TreeNodeProvider, useTreeNode } from "./hooks/tree-node-provider";
7
+
8
+ import { Button, IButtonProperties } from "../button";
9
+ import { TreeView, TreeItem, TreeNodeContent } from "./styles";
10
+
11
+ import {
12
+ IReactChildren,
13
+ IComponentSpacing,
14
+ ComponentVariantEnum,
15
+ } from "../../../../types";
16
+
17
+ export interface ITreeComposition {
18
+ Root: typeof TreeRoot;
19
+ Node: typeof TreeNode;
20
+ Trigger: typeof TreeTrigger;
21
+ Content: typeof TreeContent;
22
+ }
23
+
24
+ export interface ITreeProperties
25
+ extends IComponentSpacing, React.ComponentProps<"ul"> {}
26
+
27
+ export interface ITreeRootProperties extends IReactChildren {
28
+ defaultExpandedIds?: string[];
29
+ onSelectionChange?: (ids: string[]) => void;
30
+ }
31
+
32
+ export interface ITreeNodeProperties
33
+ extends IComponentSpacing, React.ComponentProps<"li"> {
34
+ nodeId: string;
35
+ level?: number;
36
+ isLast?: boolean;
37
+ }
38
+
39
+ export interface ITreeTriggerProperties extends Omit<
40
+ IButtonProperties,
41
+ "value"
42
+ > {
43
+ nodeId: string;
44
+ }
45
+
46
+ export interface ITreeContentProperties
47
+ extends IComponentSpacing, React.ComponentProps<"ul"> {
48
+ nodeId: string;
49
+ defaultOpen?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Tree is used to display a hierarchical list of items.
54
+ *
55
+ * **Best practices:**
56
+ *
57
+ * - Use a clear and descriptive label for each tree node.
58
+ * - Ensure that the tree can be operated using only the keyboard.
59
+ * - Ensure that the focus is properly managed when nodes are expanded/collapsed.
60
+ *
61
+ * @param {ITreeProperties} props - The props for the Tree component.
62
+ * @param {ReactNode} props.children - The content to be rendered inside the tree.
63
+ * @returns {ReactElement} The Tree component.
64
+ */
65
+ const Tree = (props: ITreeProperties) => {
66
+ const { children, ...restProps } = props;
67
+ const { id } = useTree();
68
+
69
+ return (
70
+ <TreeView id={id} role="tree" {...restProps}>
71
+ {children}
72
+ </TreeView>
73
+ );
74
+ };
75
+ Tree.displayName = "Tree";
76
+
77
+ const TreeRoot = ({
78
+ children,
79
+ defaultExpandedIds,
80
+ onSelectionChange,
81
+ }: ITreeRootProperties) => {
82
+ return (
83
+ <TreeProvider
84
+ defaultExpandedIds={defaultExpandedIds}
85
+ onSelectionChange={onSelectionChange}
86
+ >
87
+ {children}
88
+ </TreeProvider>
89
+ );
90
+ };
91
+ TreeRoot.displayName = "Tree.Root";
92
+
93
+ /**
94
+ * Tree.Node is used to wrap each node of the tree.
95
+ *
96
+ * **Best practices:**
97
+ *
98
+ * - Provide a unique nodeId for each node.
99
+ * - Use the level prop to indicate the depth of the node in the hierarchy.
100
+ *
101
+ * @param {ITreeNodeProperties} props - The props for the Tree.Node component.
102
+ * @param {string} props.nodeId - The unique identifier for the node.
103
+ * @param {number} props.level - The depth level of the node. Defaults to 0.
104
+ * @param {boolean} props.isLast - Whether the node is the last in its siblings. Defaults to false.
105
+ * @param {ReactNode} props.children - The content to be rendered inside the node.
106
+ * @returns {ReactElement} The Tree.Node component.
107
+ */
108
+ const TreeNode = (props: ITreeNodeProperties) => {
109
+ const { nodeId, level = 0, isLast = false, children, ...restProps } = props;
110
+
111
+ return (
112
+ <TreeNodeProvider nodeId={nodeId} level={level} isLast={isLast}>
113
+ <TreeItem role="treeitem" aria-level={level + 1} {...restProps}>
114
+ {children}
115
+ </TreeItem>
116
+ </TreeNodeProvider>
117
+ );
118
+ };
119
+ TreeNode.displayName = "Tree.Node";
120
+
121
+ /**
122
+ * Tree.Trigger is used to trigger the expansion and collapse of the associated Tree.Content component.
123
+ *
124
+ * **Best practices:**
125
+ *
126
+ * - Use a clear and descriptive label for the trigger.
127
+ * - Ensure that the trigger can be operated using only the keyboard.
128
+ * - Ensure that the focus is properly managed when the trigger is activated.
129
+ *
130
+ * @param {ITreeTriggerProperties} props - The props for the Tree.Trigger component.
131
+ * @param {string} props.nodeId - The value used to bind the Tree.Trigger and Tree.Content components.
132
+ * @param {ReactNode} props.children - The content to be rendered inside the trigger.
133
+ * @returns {ReactElement} The Tree.Trigger component.
134
+ */
135
+ const TreeTrigger = (props: ITreeTriggerProperties) => {
136
+ const { nodeId, disabled, onClick, children, ...restProps } = props;
137
+
138
+ const { methods } = useTree();
139
+ const { getTreeId, toggleExpanded, toggleSelected } = methods;
140
+
141
+ const isExpanded = methods.isExpanded && methods.isExpanded(nodeId);
142
+ const isSelected = methods.isSelected && methods.isSelected(nodeId);
143
+
144
+ const IdHandler = {
145
+ trigger: getTreeId && getTreeId({ nodeId, type: "trigger" }),
146
+ content: getTreeId && getTreeId({ nodeId, type: "content" }),
147
+ };
148
+
149
+ const { states: nodeStates } = useTreeNode();
150
+ const level = nodeStates.level ?? 0;
151
+
152
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
153
+ if (!disabled) {
154
+ onClick && onClick(event);
155
+ toggleExpanded && toggleExpanded(nodeId);
156
+ toggleSelected && toggleSelected(nodeId);
157
+ }
158
+ };
159
+
160
+ return (
161
+ <Button
162
+ id={String(IdHandler.trigger)}
163
+ disabled={disabled ?? false}
164
+ onClick={handleClick}
165
+ data-state={isExpanded ? "expanded" : "collapsed"}
166
+ data-selected={isSelected || undefined}
167
+ variant={props.variant ?? ComponentVariantEnum.Ghost}
168
+ style={{ paddingLeft: `calc(${level} * 1rem + 0.5rem)` }}
169
+ rawicon
170
+ {...restProps}
171
+ >
172
+ {children}
173
+ </Button>
174
+ );
175
+ };
176
+ TreeTrigger.displayName = "Tree.Trigger";
177
+
178
+ /**
179
+ * Tree.Content is used to contain the children of the associated Tree.Trigger component.
180
+ *
181
+ * **Best practices:**
182
+ *
183
+ * - Ensure that the content is hidden when the associated node is collapsed.
184
+ * - Ensure that the content is properly focused when the associated node is expanded.
185
+ *
186
+ * @param {ITreeContentProperties} props - The props for the Tree.Content component.
187
+ * @param {string} props.nodeId - The value used to bind the Tree.Content and Tree.Trigger components.
188
+ * @param {boolean} props.defaultOpen - The initial open state of the content. Defaults to false.
189
+ * @param {ReactNode} props.children - The content to be rendered inside the node content.
190
+ * @returns {ReactElement} The Tree.Content component.
191
+ */
192
+ const TreeContent = (props: ITreeContentProperties) => {
193
+ const { nodeId, defaultOpen = false, children, ...restProps } = props;
194
+
195
+ const { methods } = useTree();
196
+ const { getTreeId, toggleExpanded } = methods;
197
+
198
+ const isExpanded = methods.isExpanded && methods.isExpanded(nodeId);
199
+
200
+ const IdHandler = {
201
+ trigger: getTreeId && getTreeId({ nodeId, type: "trigger" }),
202
+ content: getTreeId && getTreeId({ nodeId, type: "content" }),
203
+ };
204
+
205
+ React.useEffect(() => {
206
+ if (defaultOpen && !isExpanded && toggleExpanded) toggleExpanded(nodeId);
207
+ }, []);
208
+
209
+ if (isExpanded)
210
+ return (
211
+ <TreeNodeContent
212
+ role="group"
213
+ id={String(IdHandler.content)}
214
+ aria-labelledby={String(IdHandler.trigger)}
215
+ data-nodeId={nodeId}
216
+ {...restProps}
217
+ >
218
+ {children}
219
+ </TreeNodeContent>
220
+ );
221
+
222
+ return <React.Fragment />;
223
+ };
224
+ TreeContent.displayName = "Tree.Content";
225
+
226
+ Tree.Root = TreeRoot;
227
+ Tree.Node = TreeNode;
228
+ Tree.Trigger = TreeTrigger;
229
+ Tree.Content = TreeContent;
230
+
231
+ export { Tree, TreeRoot, TreeNode, TreeTrigger, TreeContent };
@@ -0,0 +1,23 @@
1
+ import styled from "styled-components";
2
+
3
+ export const TreeView = styled.ul<any>`
4
+ display: flex;
5
+ flex-direction: column;
6
+ list-style: none;
7
+ margin: 0;
8
+ padding: 0;
9
+ `;
10
+
11
+ export const TreeItem = styled.li<any>`
12
+ display: flex;
13
+ flex-direction: column;
14
+ list-style: none;
15
+ `;
16
+
17
+ export const TreeNodeContent = styled.ul<any>`
18
+ display: flex;
19
+ flex-direction: column;
20
+ list-style: none;
21
+ margin: 0;
22
+ padding: 0;
23
+ `;
@@ -0,0 +1,20 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "outDir": "./dist",
6
+ "declaration": true,
7
+ "declarationDir": "./dist",
8
+ "declarationMap": true,
9
+ "sourceMap": true
10
+ },
11
+ "exclude": [
12
+ "node_modules",
13
+ "**/*.test.ts",
14
+ "**/*.test.tsx",
15
+ "**/*.spec.ts",
16
+ "**/*.spec.tsx",
17
+ "**/*.stories.ts",
18
+ "**/*.stories.tsx"
19
+ ]
20
+ }