@usefui/components 1.7.1 → 1.7.2

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.
@@ -290,12 +290,8 @@ export const InnerWrapper = styled.div<any>`
290
290
  display: flex;
291
291
  flex-direction: column;
292
292
 
293
- right: 0;
294
- &[data-multiple="true"] {
295
- right: var(--measurement-small-10) !important;
296
- }
297
-
298
293
  top: var(--measurement-small-10);
294
+ right: var(--measurement-small-10);
299
295
  bottom: var(--measurement-small-10);
300
296
 
301
297
  border-left: var(--measurement-small-10) solid var(--font-color-alpha-10);
@@ -306,7 +302,6 @@ export const InnerWrapper = styled.div<any>`
306
302
  &[data-error="true"] {
307
303
  border-color: var(--alpha-red-10) !important;
308
304
  }
309
-
310
305
  &[data-shape="round"] {
311
306
  border-radius: 0 var(--measurement-large-90) var(--measurement-large-90) 0;
312
307
  }
@@ -332,16 +327,7 @@ export const InnerTrigger = styled.button<any>`
332
327
  padding: 0 var(--measurement-medium-40);
333
328
 
334
329
  color: var(--font-color-alpha-60);
335
-
336
- background-color: var(--body-color);
337
- background: linear-gradient(
338
- 180deg,
339
- transparent 50%,
340
- var(--font-color-alpha-10) 100%
341
- );
342
- background-size: 100% 200%;
343
- background-position: 0% 0%;
344
- backdrop-filter: blur(var(--measurement-medium-10));
330
+ backdrop-filter: blur(var(--measurement-small-60));
345
331
 
346
332
  cursor: pointer;
347
333
  transition: all ease-in-out 0.2s;
@@ -351,7 +337,7 @@ export const InnerTrigger = styled.button<any>`
351
337
  transition: all ease-in-out 0.2s;
352
338
  }
353
339
 
354
- ::before {
340
+ /* ::before {
355
341
  content: "";
356
342
  inset: 0;
357
343
 
@@ -365,12 +351,12 @@ export const InnerTrigger = styled.button<any>`
365
351
 
366
352
  transition: all ease-in-out 0.2s;
367
353
  mask-image: linear-gradient(var(--font-color), transparent);
368
- }
354
+ } */
369
355
 
370
356
  &:hover,
371
357
  &:active {
372
358
  color: var(--font-color);
373
- background-position: 0% 50%;
359
+ /* background-position: 0% 50%; */
374
360
 
375
361
  svg {
376
362
  opacity: 0.8;
@@ -390,7 +376,7 @@ export const InnerSegment = styled.span<any>`
390
376
  text-align: center;
391
377
  outline: none;
392
378
  color: inherit;
393
- transition: background-color ease-in-out 0.2s;
379
+ /* transition: background-color ease-in-out 0.2s; */
394
380
 
395
381
  &[data-placeholder="true"] {
396
382
  color: var(--font-color-alpha-30);
@@ -401,7 +387,7 @@ export const InnerSegment = styled.span<any>`
401
387
  &:active,
402
388
  &:focus-within,
403
389
  &:has(:active) {
404
- background-color: var(--font-color-alpha-10);
390
+ /* background-color: var(--font-color-alpha-10); */
405
391
  color: var(--font-color);
406
392
  }
407
393
  }
package/src/index.ts CHANGED
@@ -29,7 +29,6 @@ export * from "./text-area";
29
29
  export * from "./toggle";
30
30
  export * from "./toolbar";
31
31
  export * from "./tooltip";
32
- export * from "./tree";
33
32
  export * from "./select";
34
33
 
35
34
  export { useAccordion } from "./accordion/hooks";
@@ -43,6 +42,4 @@ export { useSwitch } from "./switch/hooks";
43
42
  export { useTabs } from "./tabs/hooks";
44
43
  export { useToolbar } from "./toolbar/hooks";
45
44
  export { useMessageBubble } from "./message-bubble/hooks";
46
- export { useTree } from "./tree/hooks/tree-provider";
47
- export { useTreeNode } from "./tree/hooks/tree-node-provider";
48
45
  export { useSelect } from "./select/hooks";
@@ -76,33 +76,48 @@ export const Conversation: Story = {
76
76
  {(
77
77
  [
78
78
  {
79
- variant: "border",
79
+ variant: "primary",
80
80
  side: "left",
81
81
  message: "Hey, how are you doing?",
82
82
  },
83
83
  {
84
- variant: "primary",
84
+ variant: "meta",
85
85
  side: "right",
86
86
  message: "All good! What about you?",
87
87
  },
88
88
  {
89
- variant: "border",
89
+ variant: "primary",
90
90
  side: "left",
91
91
  message: "Pretty great, thanks for asking",
92
92
  },
93
93
  {
94
- variant: "primary",
94
+ variant: "meta",
95
95
  side: "right",
96
96
  message:
97
97
  "Hic dolorum esse magnam sint quibusdam porro reprehenderit, enim, repellendus ipsam, iste est! Deserunt ipsam ullam dolores expedita rem, magni iste eveniet.",
98
98
  },
99
+ {
100
+ variant: "meta",
101
+ side: "right",
102
+ message: "Hic dolorum esse magnam sint quibusdam.",
103
+ },
99
104
  {
100
105
  variant: "hint",
101
106
  side: "right",
102
107
  message: "Hic dolorum esse magnam sint quibusdam.",
103
108
  },
104
109
  {
105
- variant: "meta",
110
+ variant: "success",
111
+ side: "right",
112
+ message: "Hic dolorum esse magnam sint quibusdam.",
113
+ },
114
+ {
115
+ variant: "warning",
116
+ side: "right",
117
+ message: "Ipsa nisi fugiat doloribus.",
118
+ },
119
+ {
120
+ variant: "danger",
106
121
  side: "right",
107
122
  message: "Ipsa nisi fugiat doloribus.",
108
123
  },
@@ -116,7 +131,7 @@ export const Conversation: Story = {
116
131
  sizing="small"
117
132
  alt="foundation-logo"
118
133
  src="https://www.untitledui.com/images/avatars/olivia-rhye?fm=webp&q=80"
119
- shape="smooth"
134
+ // shape="smooth"
120
135
  >
121
136
  <Avatar.Badge
122
137
  alt="foundation-logo"
@@ -126,7 +141,12 @@ export const Conversation: Story = {
126
141
  </Field.Meta>
127
142
  )}
128
143
 
129
- <MessageBubble.Content variant={variant} className="fs-medium-20">
144
+ <MessageBubble.Content
145
+ variant={variant}
146
+ className="fs-medium-20"
147
+ shape="round"
148
+ emphasis
149
+ >
130
150
  {message}
131
151
  </MessageBubble.Content>
132
152
  <MessageBubble.Meta createdAt={MOCK_DATE} />
@@ -40,6 +40,7 @@ export interface IMessageBubbleContentProperties
40
40
  IComponentSize,
41
41
  React.HTMLAttributes<HTMLDivElement> {
42
42
  variant?: TComponentVariant | TComponentVariantExtended;
43
+ emphasis?: boolean;
43
44
  children: string;
44
45
  }
45
46
 
@@ -99,11 +100,13 @@ MessageBubble.displayName = "MessageBubble";
99
100
  *
100
101
  * @param {IMessageBubbleContentProperties} props - The props for the MessageBubble.Content component.
101
102
  * @param {boolean} props.raw - When true, removes default styling for custom layouts.
103
+ * @param {boolean} props.emphasis - Emphasis change the style definition used by the component.
102
104
  * @param {ReactNode} props.children - The message text or rich content to render.
103
105
  * @returns {ReactElement} The MessageBubble.Content component.
104
106
  */
105
107
  const MessageBubbleContent = (props: IMessageBubbleContentProperties) => {
106
- const { sizing, shape, variant, children, raw, ...restProps } = props;
108
+ const { emphasis, sizing, shape, variant, children, raw, ...restProps } =
109
+ props;
107
110
  const { id, states } = useMessageBubble();
108
111
 
109
112
  return (
@@ -111,8 +114,9 @@ const MessageBubbleContent = (props: IMessageBubbleContentProperties) => {
111
114
  data-raw={Boolean(raw)}
112
115
  data-side={states?.side}
113
116
  variant={variant ?? ComponentVariantEnum.Border}
114
- shape={shape ?? ComponentShapeEnum.Smooth}
117
+ shape={shape ?? ComponentShapeEnum.Round}
115
118
  sizing={sizing ?? ComponentSizeEnum.Medium}
119
+ emphasis={emphasis}
116
120
  aria-label={`message-bubble-content-${id}`}
117
121
  {...restProps}
118
122
  >
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import type { Meta, StoryObj } from "@storybook/react";
2
+
3
3
  import { Page, Resizable } from "..";
4
4
 
5
5
  /**
@@ -9,31 +9,28 @@ const meta = {
9
9
  title: "Components/Resizable",
10
10
  component: Resizable,
11
11
  tags: ["autodocs"],
12
- } satisfies Meta<typeof Resizable>;
12
+ };
13
13
  export default meta;
14
14
 
15
- type Story = StoryObj<typeof meta>;
16
- export const Default: Story = {
17
- args: {
18
- defaultWidth: 30,
19
- left: (
20
- <div
21
- className="h-100 w-100 flex align-center justify-center"
22
- style={{ borderRight: "1px solid var(--font-color-alpha-10)" }}
23
- >
24
- <p className="fs-medium-20">One</p>
25
- </div>
26
- ),
27
- right: (
28
- <div className="h-100 w-100 flex align-center justify-center">
29
- <p className="fs-medium-20">Two</p>
30
- </div>
31
- ),
32
- },
33
- render: ({ ...args }) => (
15
+ export const Default = {
16
+ render: () => (
34
17
  <Page>
35
18
  <Page.Content>
36
- <Resizable {...args} />
19
+ <Resizable
20
+ defaultWidth={30}
21
+ minWidth={0}
22
+ maxWidth={50}
23
+ left={
24
+ <div className="h-100 w-100 flex align-center justify-center bg-mono-darkest">
25
+ One
26
+ </div>
27
+ }
28
+ right={
29
+ <div className="h-100 w-100 flex align-center justify-center">
30
+ Two
31
+ </div>
32
+ }
33
+ />
37
34
  </Page.Content>
38
35
  </Page>
39
36
  ),
@@ -13,6 +13,8 @@ import {
13
13
 
14
14
  type ResizableEditorProperties = {
15
15
  defaultWidth?: number;
16
+ minWidth?: number;
17
+ maxWidth?: number;
16
18
  left: React.ReactNode;
17
19
  right: React.ReactNode;
18
20
  };
@@ -22,12 +24,16 @@ type ResizableEditorProperties = {
22
24
  *
23
25
  * @param {ResizableEditorProperties} props - The props for the Resizable component.
24
26
  * @param {number} props.defaultWidth - The default width of the Resizable left section.
27
+ * @param {number} props.minWidth - The minimum width of the Resizable sections, expressed as a percentage.
28
+ * @param {number} props.maxWidth - The maximum width of the Resizable sections, expressed as a percentage.
25
29
  * @param {ReactNode} props.left - The content to be rendered inside the Left panel of the Resizable component.
26
30
  * @param {ReactNode} props.right - The content to be rendered inside the Right panel of the Resizable component.
27
31
  * @returns {ReactElement} The Resizable component.
28
32
  */
29
33
  export const Resizable = ({
30
34
  defaultWidth,
35
+ minWidth,
36
+ maxWidth,
31
37
  left,
32
38
  right,
33
39
  }: ResizableEditorProperties) => {
@@ -47,16 +53,14 @@ export const Resizable = ({
47
53
  const newLeftWidth =
48
54
  ((e.clientX - containerRect.left) / containerRect.width) * 100;
49
55
 
50
- // Constrain between 20% and 80%
51
- const threshold = { min: 30, max: 70 };
52
-
56
+ const threshold = { min: minWidth ?? 30, max: maxWidth ?? 70 };
53
57
  const constrainedWidth = Math.min(
54
58
  Math.max(newLeftWidth, threshold.min),
55
- threshold.max
59
+ threshold.max,
56
60
  );
57
61
  setLeftWidth(constrainedWidth);
58
62
  },
59
- [isDragging]
63
+ [isDragging],
60
64
  );
61
65
 
62
66
  React.useEffect(() => {
@@ -5,9 +5,12 @@ import styled from "styled-components";
5
5
  export const SplitContainer = styled.div`
6
6
  position: relative;
7
7
  `;
8
- export const Panel = styled.div<{ width: number }>`
8
+ export const Panel = styled.div<{
9
+ width: number;
10
+ }>`
9
11
  overflow: hidden;
10
- width: ${(props) => props.width}%;
12
+ width: ${(props) => props.width}dvw;
13
+ min-width: var(--measurement-large-60);
11
14
  `;
12
15
  export const Divider = styled.div<{ $dragging: boolean }>`
13
16
  width: var(--measurement-medium-10);
@@ -1,275 +0,0 @@
1
- import React from "react";
2
-
3
- import { test, vi, afterEach, describe, expect } from "vitest";
4
- import {
5
- screen,
6
- render,
7
- cleanup,
8
- waitFor,
9
- fireEvent,
10
- } from "@testing-library/react";
11
- import { axe, toHaveNoViolations } from "jest-axe";
12
-
13
- import { Tree } from "../../src/tree";
14
-
15
- afterEach(async () => {
16
- vi.clearAllMocks();
17
- vi.resetModules();
18
- cleanup();
19
- });
20
-
21
- expect.extend(toHaveNoViolations);
22
-
23
- describe("Tree", () => {
24
- test("Renders without accessibility violation", async () => {
25
- const { container } = render(
26
- <Tree.Root>
27
- <Tree>
28
- <Tree.Node nodeId="node-1">
29
- <Tree.Trigger nodeId="node-1">Node 1</Tree.Trigger>
30
- <Tree.Content nodeId="node-1" defaultOpen>
31
- <Tree.Node level={1} nodeId="node-1-child">
32
- <Tree.Trigger nodeId="node-1-child">Node 1 Child</Tree.Trigger>
33
- </Tree.Node>
34
- </Tree.Content>
35
- </Tree.Node>
36
- </Tree>
37
- </Tree.Root>,
38
- );
39
-
40
- const ComponentContainer = await axe(container);
41
- expect(ComponentContainer).toHaveNoViolations();
42
- });
43
-
44
- // test("Renders with accessibility definition", async () => {
45
- // render(
46
- // <Tree.Root>
47
- // <Tree>
48
- // <Tree.Node nodeId="node-1">
49
- // <Tree.Trigger nodeId="node-1">Node 1</Tree.Trigger>
50
- // <Tree.Content nodeId="node-1">
51
- // <Tree.Node level={1} nodeId="node-1-child">
52
- // <Tree.Trigger nodeId="node-1-child">Node 1 Child</Tree.Trigger>
53
- // </Tree.Node>
54
- // </Tree.Content>
55
- // </Tree.Node>
56
- // </Tree>
57
- // </Tree.Root>,
58
- // );
59
-
60
- // const treeView = screen.getByRole("tree");
61
- // expect(treeView).toBeDefined();
62
-
63
- // const treeItems = screen.getAllByRole("treeitem");
64
- // expect(treeItems.length).toBe(1);
65
-
66
- // expect(() => screen.getByRole("group")).toThrow();
67
-
68
- // const trigger = screen.getByLabelText("node-1-action");
69
- // expect(trigger).toBeDefined();
70
- // expect(trigger.getAttribute("aria-expanded")).toBe("false");
71
- // expect(trigger.getAttribute("aria-selected")).toBe("false");
72
- // expect(trigger.getAttribute("data-state")).toBe("collapsed");
73
-
74
- // fireEvent.click(trigger);
75
- // await waitFor(() => {
76
- // const group = screen.getByRole("group");
77
- // expect(group).toBeDefined();
78
- // expect(group.getAttribute("aria-labelledby")).toBeDefined();
79
- // expect(group.getAttribute("id")).toBeDefined();
80
- // expect(group.getAttribute("data-nodeid")).toBe("node-1");
81
-
82
- // expect(trigger.getAttribute("aria-expanded")).toBe("true");
83
- // expect(trigger.getAttribute("aria-selected")).toBe("true");
84
- // expect(trigger.getAttribute("data-state")).toBe("expanded");
85
-
86
- // const childItems = screen.getAllByRole("treeitem");
87
- // expect(childItems.length).toBe(2);
88
- // });
89
- // });
90
-
91
- test("Renders with correct aria-level on nested nodes", async () => {
92
- render(
93
- <Tree.Root>
94
- <Tree>
95
- <Tree.Node nodeId="root">
96
- <Tree.Trigger nodeId="root">Root</Tree.Trigger>
97
- <Tree.Content nodeId="root" defaultOpen>
98
- <Tree.Node level={1} nodeId="child">
99
- <Tree.Trigger nodeId="child">Child</Tree.Trigger>
100
- <Tree.Content nodeId="child" defaultOpen>
101
- <Tree.Node level={2} nodeId="grandchild">
102
- <Tree.Trigger nodeId="grandchild">Grandchild</Tree.Trigger>
103
- </Tree.Node>
104
- </Tree.Content>
105
- </Tree.Node>
106
- </Tree.Content>
107
- </Tree.Node>
108
- </Tree>
109
- </Tree.Root>,
110
- );
111
-
112
- const treeItems = screen.getAllByRole("treeitem");
113
- expect((treeItems[0] as HTMLElement).getAttribute("aria-level")).toBe("1");
114
- expect((treeItems[1] as HTMLElement).getAttribute("aria-level")).toBe("2");
115
- expect((treeItems[2] as HTMLElement).getAttribute("aria-level")).toBe("3");
116
- });
117
-
118
- // test("Renders with defaultOpen and expands content on mount", async () => {
119
- // render(
120
- // <Tree.Root>
121
- // <Tree>
122
- // <Tree.Node nodeId="node-1">
123
- // <Tree.Trigger nodeId="node-1">Node 1</Tree.Trigger>
124
- // <Tree.Content nodeId="node-1" defaultOpen>
125
- // <Tree.Node level={1} nodeId="node-1-child">
126
- // <Tree.Trigger nodeId="node-1-child">Node 1 Child</Tree.Trigger>
127
- // </Tree.Node>
128
- // </Tree.Content>
129
- // </Tree.Node>
130
- // </Tree>
131
- // </Tree.Root>,
132
- // );
133
-
134
- // await waitFor(() => {
135
- // expect(screen.getByRole("group")).toBeDefined();
136
- // expect(screen.getByText("Node 1 Child")).toBeDefined();
137
-
138
- // const trigger = screen.getByLabelText("node-1-action");
139
- // expect(trigger.getAttribute("aria-expanded")).toBe("true");
140
- // expect(trigger.getAttribute("data-state")).toBe("expanded");
141
- // });
142
- // });
143
-
144
- test("Fires the defined callback and toggles content when trigger is clicked", async () => {
145
- const onClickCallback = vi.fn();
146
-
147
- render(
148
- <Tree.Root>
149
- <Tree>
150
- <Tree.Node nodeId="node-1">
151
- <Tree.Trigger
152
- nodeId="node-1"
153
- name="trigger-1"
154
- onClick={onClickCallback}
155
- >
156
- Node 1
157
- </Tree.Trigger>
158
- <Tree.Content nodeId="node-1" defaultOpen>
159
- <Tree.Node level={1} nodeId="node-1-child">
160
- <Tree.Trigger nodeId="node-1-child">Node 1 Child</Tree.Trigger>
161
- </Tree.Node>
162
- </Tree.Content>
163
- </Tree.Node>
164
- <Tree.Node nodeId="node-2">
165
- <Tree.Trigger nodeId="node-2" name="trigger-2">
166
- Node 2
167
- </Tree.Trigger>
168
- <Tree.Content nodeId="node-2">
169
- <Tree.Node level={1} nodeId="node-2-child">
170
- <Tree.Trigger nodeId="node-2-child">Node 2 Child</Tree.Trigger>
171
- </Tree.Node>
172
- </Tree.Content>
173
- </Tree.Node>
174
- </Tree>
175
- </Tree.Root>,
176
- );
177
-
178
- expect(screen.getByText("Node 1 Child")).toBeDefined();
179
- expect(() => screen.getByText("Node 2 Child")).toThrow();
180
-
181
- fireEvent.click(screen.getByLabelText("trigger-2-action"));
182
- await waitFor(() => {
183
- expect(screen.getByText("Node 2 Child")).toBeDefined();
184
- });
185
-
186
- fireEvent.click(screen.getByLabelText("trigger-1-action"));
187
- await waitFor(() => {
188
- expect(() => screen.getByText("Node 1 Child")).toThrow();
189
- expect(onClickCallback).toHaveBeenCalledTimes(1);
190
- });
191
-
192
- fireEvent.click(screen.getByLabelText("trigger-1-action"));
193
- await waitFor(() => {
194
- expect(screen.getByText("Node 1 Child")).toBeDefined();
195
- expect(onClickCallback).toHaveBeenCalledTimes(2);
196
- });
197
-
198
- fireEvent.click(screen.getByLabelText("trigger-1-action"));
199
- await waitFor(() => {
200
- expect(() => screen.getByText("Node 1 Child")).toThrow();
201
- });
202
- });
203
-
204
- test("Fires onSelectionChange callback with selected node ids", async () => {
205
- const onSelectionChange = vi.fn();
206
-
207
- render(
208
- <Tree.Root onSelectionChange={onSelectionChange}>
209
- <Tree>
210
- <Tree.Node nodeId="node-1">
211
- <Tree.Trigger nodeId="node-1" name="trigger-1">
212
- Node 1
213
- </Tree.Trigger>
214
- </Tree.Node>
215
- <Tree.Node nodeId="node-2">
216
- <Tree.Trigger nodeId="node-2" name="trigger-2">
217
- Node 2
218
- </Tree.Trigger>
219
- </Tree.Node>
220
- </Tree>
221
- </Tree.Root>,
222
- );
223
-
224
- fireEvent.click(screen.getByLabelText("trigger-1-action"));
225
- await waitFor(() => {
226
- expect(onSelectionChange).toHaveBeenCalledTimes(1);
227
- expect(onSelectionChange).toHaveBeenCalledWith(["node-1"]);
228
- });
229
-
230
- fireEvent.click(screen.getByLabelText("trigger-2-action"));
231
- await waitFor(() => {
232
- expect(onSelectionChange).toHaveBeenCalledTimes(2);
233
- expect(onSelectionChange).toHaveBeenCalledWith(["node-1", "node-2"]);
234
- });
235
-
236
- fireEvent.click(screen.getByLabelText("trigger-1-action"));
237
- await waitFor(() => {
238
- expect(onSelectionChange).toHaveBeenCalledTimes(3);
239
- expect(onSelectionChange).toHaveBeenCalledWith(["node-2"]);
240
- });
241
- });
242
-
243
- test("Renders with defaultExpandedIds and expands matching nodes on mount", async () => {
244
- render(
245
- <Tree.Root defaultExpandedIds={["node-1", "node-2"]}>
246
- <Tree>
247
- <Tree.Node nodeId="node-1">
248
- <Tree.Trigger nodeId="node-1">Node 1</Tree.Trigger>
249
- <Tree.Content nodeId="node-1">
250
- <Tree.Node level={1} nodeId="node-1-child">
251
- <Tree.Trigger nodeId="node-1-child">Node 1 Child</Tree.Trigger>
252
- </Tree.Node>
253
- </Tree.Content>
254
- </Tree.Node>
255
- <Tree.Node nodeId="node-2">
256
- <Tree.Trigger nodeId="node-2">Node 2</Tree.Trigger>
257
- <Tree.Content nodeId="node-2">
258
- <Tree.Node level={1} nodeId="node-2-child">
259
- <Tree.Trigger nodeId="node-2-child">Node 2 Child</Tree.Trigger>
260
- </Tree.Node>
261
- </Tree.Content>
262
- </Tree.Node>
263
- </Tree>
264
- </Tree.Root>,
265
- );
266
-
267
- await waitFor(() => {
268
- expect(screen.getByText("Node 1 Child")).toBeDefined();
269
- expect(screen.getByText("Node 2 Child")).toBeDefined();
270
-
271
- const groups = screen.getAllByRole("group");
272
- expect(groups.length).toBe(2);
273
- });
274
- });
275
- });