@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.d.mts +380 -52
  3. package/dist/index.d.ts +380 -52
  4. package/dist/index.js +2532 -511
  5. package/dist/index.mjs +2518 -508
  6. package/package.json +3 -3
  7. package/src/__tests__/Avatar.test.tsx +55 -55
  8. package/src/accordion/Accordion.stories.tsx +6 -4
  9. package/src/accordion/index.tsx +1 -2
  10. package/src/avatar/Avatar.stories.tsx +37 -7
  11. package/src/avatar/index.tsx +90 -19
  12. package/src/avatar/styles/index.ts +58 -12
  13. package/src/badge/Badge.stories.tsx +27 -5
  14. package/src/badge/index.tsx +21 -13
  15. package/src/badge/styles/index.ts +69 -40
  16. package/src/button/Button.stories.tsx +40 -27
  17. package/src/button/index.tsx +13 -9
  18. package/src/button/styles/index.ts +308 -47
  19. package/src/card/index.tsx +2 -4
  20. package/src/checkbox/Checkbox.stories.tsx +72 -33
  21. package/src/checkbox/index.tsx +8 -6
  22. package/src/checkbox/styles/index.ts +239 -19
  23. package/src/collapsible/Collapsible.stories.tsx +6 -4
  24. package/src/dialog/Dialog.stories.tsx +173 -31
  25. package/src/dialog/styles/index.ts +13 -8
  26. package/src/dropdown/Dropdown.stories.tsx +61 -23
  27. package/src/dropdown/index.tsx +42 -31
  28. package/src/dropdown/styles/index.ts +30 -19
  29. package/src/field/Field.stories.tsx +183 -24
  30. package/src/field/index.tsx +930 -13
  31. package/src/field/styles/index.ts +246 -14
  32. package/src/field/types/index.ts +31 -0
  33. package/src/field/utils/index.ts +201 -0
  34. package/src/index.ts +2 -1
  35. package/src/message-bubble/MessageBubble.stories.tsx +59 -12
  36. package/src/message-bubble/index.tsx +22 -4
  37. package/src/message-bubble/styles/index.ts +4 -7
  38. package/src/otp-field/OTPField.stories.tsx +22 -24
  39. package/src/otp-field/index.tsx +9 -0
  40. package/src/otp-field/styles/index.ts +114 -16
  41. package/src/otp-field/types/index.ts +9 -1
  42. package/src/overlay/styles/index.ts +1 -0
  43. package/src/ruler/Ruler.stories.tsx +43 -0
  44. package/src/ruler/constants/index.ts +3 -0
  45. package/src/ruler/hooks/index.tsx +53 -0
  46. package/src/ruler/index.tsx +239 -0
  47. package/src/ruler/styles/index.tsx +154 -0
  48. package/src/ruler/types/index.ts +17 -0
  49. package/src/select/Select.stories.tsx +91 -0
  50. package/src/select/hooks/index.tsx +71 -0
  51. package/src/select/index.tsx +331 -0
  52. package/src/select/styles/index.tsx +156 -0
  53. package/src/shimmer/Shimmer.stories.tsx +6 -4
  54. package/src/skeleton/index.tsx +7 -6
  55. package/src/spinner/Spinner.stories.tsx +29 -4
  56. package/src/spinner/index.tsx +16 -6
  57. package/src/spinner/styles/index.ts +41 -22
  58. package/src/switch/Switch.stories.tsx +46 -17
  59. package/src/switch/index.tsx +5 -8
  60. package/src/switch/styles/index.ts +45 -45
  61. package/src/tabs/Tabs.stories.tsx +43 -15
  62. package/src/text-area/Textarea.stories.tsx +45 -8
  63. package/src/text-area/index.tsx +9 -6
  64. package/src/text-area/styles/index.ts +1 -1
  65. package/src/toggle/Toggle.stories.tsx +6 -4
  66. package/src/tree/Tree.stories.tsx +6 -4
  67. package/src/privacy-field/PrivacyField.stories.tsx +0 -29
  68. package/src/privacy-field/index.tsx +0 -56
  69. package/src/privacy-field/styles/index.ts +0 -17
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
 
4
- import { DropdownMenu } from "..";
4
+ import { Badge, Divider, DropdownMenu, Page } from "..";
5
5
  import { ComponentVariantEnum, ComponentSizeEnum } from "../../../../types";
6
6
 
7
7
  // Duplicated doc: The JSDoc content isn't rendering on Storybook.
@@ -23,15 +23,18 @@ const meta = {
23
23
  tags: ["autodocs"],
24
24
  decorators: [
25
25
  (Story) => (
26
- <div className="m-medium-30">
27
- <Story />
28
- </div>
26
+ <Page>
27
+ <Page.Content className="p-medium-30 flex align-center justify-center w-100 h-100">
28
+ <Story />
29
+ </Page.Content>
30
+ </Page>
29
31
  ),
30
32
  ],
31
33
  } satisfies Meta<typeof DropdownMenu>;
32
34
  export default meta;
33
35
 
34
36
  type Story = StoryObj<typeof meta>;
37
+
35
38
  export const Default: Story = {
36
39
  args: {
37
40
  raw: false,
@@ -63,24 +66,30 @@ export const Default: Story = {
63
66
  },
64
67
  render: ({ ...args }) => (
65
68
  <DropdownMenu.Root>
66
- <DropdownMenu.Trigger>🐻‍❄️</DropdownMenu.Trigger>
67
69
  <DropdownMenu>
70
+ <DropdownMenu.Trigger variant="secondary">Actions</DropdownMenu.Trigger>
71
+
68
72
  <DropdownMenu.Content>
69
- {["🐻", "🐻‍❄️", "🦊", "🐱", "🐶"].map((item) => (
70
- <DropdownMenu.Item key={item}>{item}</DropdownMenu.Item>
71
- ))}
72
- </DropdownMenu.Content>
73
- </DropdownMenu>
74
- </DropdownMenu.Root>
75
- ),
76
- };
77
- export const DefaultOpen: Story = {
78
- render: ({ ...args }) => (
79
- <DropdownMenu.Root>
80
- <DropdownMenu.Trigger>🐻‍❄️</DropdownMenu.Trigger>
81
- <DropdownMenu>
82
- <DropdownMenu.Content defaultOpen>
83
- <DropdownMenu.Item>🐻🐻‍❄️🦊🐱🐶</DropdownMenu.Item>
73
+ <DropdownMenu.Item className="flex justify-between align-center">
74
+ Cut
75
+ <Badge variant="border">
76
+ <span className="fs-small-50">⌘X</span>
77
+ </Badge>
78
+ </DropdownMenu.Item>
79
+
80
+ <DropdownMenu.Item className="flex justify-between align-center">
81
+ Copy
82
+ <Badge variant="border">
83
+ <span className="fs-small-50">⌘C</span>
84
+ </Badge>
85
+ </DropdownMenu.Item>
86
+
87
+ <DropdownMenu.Item className="flex justify-between align-center">
88
+ Paste
89
+ <Badge variant="border">
90
+ <span className="fs-small-50">⌘V</span>
91
+ </Badge>
92
+ </DropdownMenu.Item>
84
93
  </DropdownMenu.Content>
85
94
  </DropdownMenu>
86
95
  </DropdownMenu.Root>
@@ -89,10 +98,39 @@ export const DefaultOpen: Story = {
89
98
  export const RadioItem: Story = {
90
99
  render: ({ ...args }) => (
91
100
  <DropdownMenu.Root>
92
- <DropdownMenu.Trigger>🐻‍❄️</DropdownMenu.Trigger>
93
101
  <DropdownMenu>
94
- <DropdownMenu.Content>
95
- <DropdownMenu.Item radio>🐻🐻‍❄️🦊🐱🐶</DropdownMenu.Item>
102
+ <DropdownMenu.Trigger variant="secondary">Actions</DropdownMenu.Trigger>
103
+
104
+ <DropdownMenu.Content className="flex flex-column g-small-60">
105
+ <DropdownMenu.Item
106
+ className="flex justify-between align-center"
107
+ radio
108
+ >
109
+ Cut
110
+ <Badge variant="border">
111
+ <span className="fs-small-50">⌘X</span>
112
+ </Badge>
113
+ </DropdownMenu.Item>
114
+
115
+ <DropdownMenu.Item
116
+ className="flex justify-between align-center"
117
+ radio
118
+ >
119
+ Copy
120
+ <Badge variant="border">
121
+ <span className="fs-small-50">⌘C</span>
122
+ </Badge>
123
+ </DropdownMenu.Item>
124
+
125
+ <DropdownMenu.Item
126
+ className="flex justify-between align-center"
127
+ radio
128
+ >
129
+ Paste
130
+ <Badge variant="border">
131
+ <span className="fs-small-50">⌘V</span>
132
+ </Badge>
133
+ </DropdownMenu.Item>
96
134
  </DropdownMenu.Content>
97
135
  </DropdownMenu>
98
136
  </DropdownMenu.Root>
@@ -203,38 +203,49 @@ const DropdownMenuContent = React.forwardRef<
203
203
  // eslint-disable-next-line react-hooks/exhaustive-deps
204
204
  }, [states.open]);
205
205
 
206
+ React.useEffect(() => {
207
+ if (!states.open) return;
208
+
209
+ const handleKeyDown = (event: KeyboardEvent) => {
210
+ if (event.key === "Escape" && methods.toggleOpen) {
211
+ methods.toggleOpen();
212
+ }
213
+ };
214
+
215
+ document.addEventListener("keydown", handleKeyDown);
216
+ return () => document.removeEventListener("keydown", handleKeyDown);
217
+ // eslint-disable-next-line react-hooks/exhaustive-deps
218
+ }, [states.open]);
219
+
220
+ if (!states.open) return null;
206
221
  return (
207
- <>
208
- {states.open && (
209
- <ContentWrapper
210
- ref={contentRef}
211
- id={id.split("|").at(-1)}
212
- role="menu"
213
- tabIndex={-1}
214
- aria-labelledby={id.split("|").at(0)}
215
- data-state={applyDataState(Boolean(states.open))}
216
- data-sizing={sizing}
217
- data-side={
218
- hasEnoughHorizontalSpace
219
- ? ComponentSideEnum.Left
220
- : ComponentSideEnum.Right
221
- }
222
- data-align={
223
- hasEnoughHorizontalSpace
224
- ? ComponentSideEnum.Left
225
- : ComponentSideEnum.Right
226
- }
227
- data-raw={Boolean(raw)}
228
- style={{
229
- top: hasEnoughVerticalSpace ? positions.ttb : positions.btt,
230
- left: hasEnoughHorizontalSpace ? positions.ltr : positions.rtl,
231
- }}
232
- {...restProps}
233
- >
234
- {children}
235
- </ContentWrapper>
236
- )}
237
- </>
222
+ <ContentWrapper
223
+ ref={contentRef}
224
+ id={id.split("|").at(-1)}
225
+ role="menu"
226
+ tabIndex={-1}
227
+ aria-labelledby={id.split("|").at(0)}
228
+ data-state={applyDataState(Boolean(states.open))}
229
+ data-sizing={sizing}
230
+ data-side={
231
+ hasEnoughHorizontalSpace
232
+ ? ComponentSideEnum.Left
233
+ : ComponentSideEnum.Right
234
+ }
235
+ data-align={
236
+ hasEnoughHorizontalSpace
237
+ ? ComponentSideEnum.Left
238
+ : ComponentSideEnum.Right
239
+ }
240
+ data-raw={Boolean(raw)}
241
+ style={{
242
+ top: hasEnoughVerticalSpace ? positions.ttb : positions.btt,
243
+ left: hasEnoughHorizontalSpace ? positions.ltr : positions.rtl,
244
+ }}
245
+ {...restProps}
246
+ >
247
+ {children}
248
+ </ContentWrapper>
238
249
  );
239
250
  });
240
251
  DropdownMenuContent.displayName = "DropdownMenu.Content";
@@ -1,13 +1,5 @@
1
- import styled, { css, keyframes } from "styled-components";
1
+ import styled, { css } from "styled-components";
2
2
 
3
- const FadeIn = keyframes`
4
- 0% {
5
- opacity: 0;
6
- }
7
- 100% {
8
- opacity: 1;
9
- }
10
- `;
11
3
  const ContentWrapperSizes = css`
12
4
  --small: var(--measurement-large-60);
13
5
  --medium: var(--measurement-large-80);
@@ -39,6 +31,17 @@ export const ContentWrapper = styled.ul<any>`
39
31
  --medium: var(--measurement-large-80);
40
32
  --large: var(--measurement-large-90);
41
33
 
34
+ @keyframes slide-in {
35
+ 0% {
36
+ opacity: 0;
37
+ transform: translateY(calc(var(--measurement-small-60) * -1));
38
+ }
39
+ 100% {
40
+ opacity: 1;
41
+ transform: translateY(0);
42
+ }
43
+ }
44
+
42
45
  &[data-raw="false"] {
43
46
  position: fixed;
44
47
  margin: 0;
@@ -51,11 +54,11 @@ export const ContentWrapper = styled.ul<any>`
51
54
  border-radius: var(--measurement-medium-30);
52
55
 
53
56
  z-index: var(--depth-default-100);
54
- animation-duration: 0.2s;
55
- animation-name: ${FadeIn};
56
- animation-fill-mode: backwards;
57
57
 
58
58
  ${ContentWrapperSizes}
59
+ animation-duration: 0.2s;
60
+ animation-name: slide-in;
61
+ animation-fill-mode: backwards;
59
62
  }
60
63
  `;
61
64
 
@@ -66,20 +69,28 @@ export const ItemWrapper = styled.li<any>`
66
69
  user-select: none;
67
70
 
68
71
  &[data-raw="false"] {
69
- font-size: var(--fontsize-small-80);
70
- padding: var(--measurement-medium-30);
72
+ padding: var(--measurement-medium-10) var(--measurement-medium-30);
71
73
  border-radius: var(--measurement-medium-20);
74
+
72
75
  text-align: left;
73
- color: var(--font-color-alpha-60);
76
+ font-weight: 600;
77
+ letter-spacing: calc(
78
+ var(--fontsize-small-10) - ((var(--fontsize-small-10) * 1.066))
79
+ );
80
+ font-size: var(--fontsize-medium-10);
81
+ color: var(--font-color);
82
+
74
83
  outline: none;
75
- transition: all ease-in-out 0.2s;
76
84
  cursor: pointer;
77
85
 
86
+ transition: all ease-in-out 0.2s;
87
+
78
88
  &:hover,
79
89
  &:focus,
80
- &:active {
81
- color: var(--font-color);
82
- background-color: var(--font-color-alpha-10);
90
+ &:active,
91
+ &:focus-within,
92
+ &:has(:active) {
93
+ background-color: var(--contrast-color);
83
94
  }
84
95
  }
85
96
 
@@ -10,9 +10,11 @@ const meta = {
10
10
  tags: ["autodocs"],
11
11
  decorators: [
12
12
  (Story) => (
13
- <div className="m-medium-30">
14
- <Story />
15
- </div>
13
+ <Page>
14
+ <Page.Content className="p-medium-30">
15
+ <Story />
16
+ </Page.Content>
17
+ </Page>
16
18
  ),
17
19
  ],
18
20
  } satisfies Meta<typeof Field>;
@@ -121,13 +123,19 @@ export const ComposedError = {
121
123
  export const Sizes = {
122
124
  render: ({ ...args }) => {
123
125
  return (
124
- <div className="flex g-medium-30">
125
- {["large", "medium", "small"].map((item) => (
126
- <Field.Root key={item}>
127
- <Field placeholder={item} sizing={item} />
128
- </Field.Root>
129
- ))}
130
- </div>
126
+ <Page>
127
+ <Page.Content>
128
+ <div className="flex flex-column align-center justify-center h-100 g-medium-30">
129
+ {["large", "medium", "small"].map((item) => (
130
+ <Field.Root key={item}>
131
+ <Field.Wrapper style={{ width: 325 }}>
132
+ <Field sizing={item} placeholder={item} variant="secondary" />
133
+ </Field.Wrapper>
134
+ </Field.Root>
135
+ ))}
136
+ </div>
137
+ </Page.Content>
138
+ </Page>
131
139
  );
132
140
  },
133
141
  };
@@ -136,15 +144,17 @@ export const Shapes = {
136
144
  return (
137
145
  <Page>
138
146
  <Page.Content>
139
- <div className="flex align-center justify-center flex-wrap h-100 g-medium-30">
147
+ <div className="flex flex-column align-center justify-center h-100 g-medium-30">
140
148
  {["square", "smooth", "round"].map((item) => (
141
149
  <Field.Root key={item}>
142
- <Field
143
- shape={item}
144
- sizing="medium"
145
- placeholder={item}
146
- variant="secondary"
147
- />
150
+ <Field.Wrapper style={{ width: 325 }}>
151
+ <Field
152
+ shape={item}
153
+ sizing="medium"
154
+ placeholder={item}
155
+ variant="secondary"
156
+ />
157
+ </Field.Wrapper>
148
158
  </Field.Root>
149
159
  ))}
150
160
  </div>
@@ -158,15 +168,17 @@ export const Variants = {
158
168
  return (
159
169
  <Page>
160
170
  <Page.Content>
161
- <div className="flex align-center justify-center flex-wrap h-100 g-medium-30">
171
+ <div className="flex flex-column align-center justify-center h-100 g-medium-30">
162
172
  {["primary", "secondary", "ghost"].map((item) => (
163
173
  <Field.Root key={item}>
164
- <Field
165
- shape="smooth"
166
- sizing="medium"
167
- placeholder={item}
168
- variant={item}
169
- />
174
+ <Field.Wrapper style={{ width: 325 }}>
175
+ <Field
176
+ shape="smooth"
177
+ sizing="medium"
178
+ placeholder={item}
179
+ variant={item}
180
+ />
181
+ </Field.Wrapper>
170
182
  </Field.Root>
171
183
  ))}
172
184
  </div>
@@ -175,3 +187,150 @@ export const Variants = {
175
187
  );
176
188
  },
177
189
  };
190
+ export const Text = {
191
+ render: ({ ...args }) => {
192
+ return (
193
+ <Page>
194
+ <Page.Content>
195
+ <div className="flex flex-column align-center justify-center flex-wrap h-100 w-100 g-medium-30">
196
+ <Field.Root>
197
+ <Field.Wrapper style={{ width: 325 }}>
198
+ <Field.Label>Text</Field.Label>
199
+ <Field variant="secondary" placeholder="Placeholder..." />
200
+ <Field.Meta variant="hint">
201
+ This is a hint text to help user.
202
+ </Field.Meta>
203
+ </Field.Wrapper>
204
+ </Field.Root>
205
+ </div>
206
+ </Page.Content>
207
+ </Page>
208
+ );
209
+ },
210
+ };
211
+ export const Number = {
212
+ render: ({ ...args }) => {
213
+ return (
214
+ <Page>
215
+ <Page.Content>
216
+ <div className="flex flex-column align-center justify-center flex-wrap h-100 w-100 g-medium-30">
217
+ <Field.Root>
218
+ <Field.Wrapper style={{ width: 325 }}>
219
+ <Field.Label>Number</Field.Label>
220
+ <Field.Number placeholder="100" min={0} max={100} />
221
+ <Field.Meta variant="hint">
222
+ This is a hint text to help user.
223
+ </Field.Meta>
224
+ </Field.Wrapper>
225
+ </Field.Root>
226
+ </div>
227
+ </Page.Content>
228
+ </Page>
229
+ );
230
+ },
231
+ };
232
+
233
+ export const Date = {
234
+ render: ({ ...args }) => {
235
+ return (
236
+ <Page>
237
+ <Page.Content>
238
+ <div className="flex flex-column align-center justify-center flex-wrap h-100 w-100 g-medium-30">
239
+ <Field.Root>
240
+ <Field.Wrapper style={{ width: 325 }}>
241
+ <Field.Label>Date</Field.Label>
242
+ <Field.Date
243
+ variant="secondary"
244
+ // defaultValue={new Date()}
245
+ locale="en-US"
246
+ withTime
247
+ />
248
+ <Field.Meta variant="hint">
249
+ This is a hint text to help user.
250
+ </Field.Meta>
251
+ </Field.Wrapper>
252
+ </Field.Root>
253
+ </div>
254
+ </Page.Content>
255
+ </Page>
256
+ );
257
+ },
258
+ };
259
+ export const File = {
260
+ render: ({ ...args }) => {
261
+ return (
262
+ <Page>
263
+ <Page.Content>
264
+ <div className="flex flex-column align-center justify-center flex-wrap h-100 w-100 g-medium-30">
265
+ <Field.Root>
266
+ <Field.Wrapper style={{ width: 325 }}>
267
+ <Field.Label>Upload file</Field.Label>
268
+ <Field.File
269
+ variant="secondary"
270
+ sizing="medium"
271
+ trigger={
272
+ <span className="fs-small-50 flex align-center justify-center w-100 h-100">
273
+ Upload
274
+ </span>
275
+ }
276
+ onFileChange={(files) => console.log(files)}
277
+ multiple
278
+ />
279
+ <Field.Meta variant="hint">
280
+ SVG, PNG, JPG or GIF (max. 800x400px).
281
+ </Field.Meta>
282
+ </Field.Wrapper>
283
+ </Field.Root>
284
+ </div>
285
+ </Page.Content>
286
+ </Page>
287
+ );
288
+ },
289
+ };
290
+ export const Password = {
291
+ render: ({ ...args }) => {
292
+ return (
293
+ <Page>
294
+ <Page.Content>
295
+ <div className="flex flex-column align-center justify-center flex-wrap h-100 w-100 g-medium-30">
296
+ <Field.Root>
297
+ <Field.Wrapper style={{ width: 325 }}>
298
+ <Field.Label>Password</Field.Label>
299
+ <Field.Password variant="secondary" sizing="medium" />
300
+ <Field.Meta variant="hint">
301
+ Must be at least 8 characters.
302
+ </Field.Meta>
303
+ </Field.Wrapper>
304
+ </Field.Root>
305
+ </div>
306
+ </Page.Content>
307
+ </Page>
308
+ );
309
+ },
310
+ };
311
+ export const Tag = {
312
+ render: ({ ...args }) => {
313
+ return (
314
+ <Page>
315
+ <Page.Content>
316
+ <div className="flex flex-column align-center justify-center flex-wrap h-100 w-100 g-medium-30">
317
+ <Field.Root>
318
+ <Field.Wrapper style={{ width: 325 }}>
319
+ <Field.Label>Tags</Field.Label>
320
+ <Field.Tag
321
+ defaultValue={["Design", "Engineering"]}
322
+ onChange={(tags) => console.log(tags)}
323
+ placeholder="Type and press Enter…"
324
+ allowed={["Design", "Engineering", "UI", "UX", "AI"]}
325
+ />
326
+ <Field.Meta variant="hint">
327
+ This is a hint text to help user.
328
+ </Field.Meta>
329
+ </Field.Wrapper>
330
+ </Field.Root>
331
+ </div>
332
+ </Page.Content>
333
+ </Page>
334
+ );
335
+ },
336
+ };