@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
@@ -10,7 +10,17 @@ import {
10
10
  MessageBubbleMetaWrapper,
11
11
  } from "./styles";
12
12
 
13
- import { IComponentStyling, IReactChildren } from "../../../../types";
13
+ import {
14
+ ComponentShapeEnum,
15
+ ComponentSizeEnum,
16
+ ComponentVariantEnum,
17
+ IComponentShape,
18
+ IComponentSize,
19
+ IComponentStyling,
20
+ IReactChildren,
21
+ TComponentVariant,
22
+ TComponentVariantExtended,
23
+ } from "../../../../types";
14
24
 
15
25
  export type MessageBubbleSide = "left" | "right";
16
26
 
@@ -24,7 +34,12 @@ export interface IMessageBubbleProperties
24
34
  }
25
35
 
26
36
  export interface IMessageBubbleContentProperties
27
- extends IComponentStyling, React.HTMLAttributes<HTMLDivElement> {
37
+ extends
38
+ IComponentStyling,
39
+ IComponentShape,
40
+ IComponentSize,
41
+ React.HTMLAttributes<HTMLDivElement> {
42
+ variant?: TComponentVariant | TComponentVariantExtended;
28
43
  children: string;
29
44
  }
30
45
 
@@ -88,14 +103,16 @@ MessageBubble.displayName = "MessageBubble";
88
103
  * @returns {ReactElement} The MessageBubble.Content component.
89
104
  */
90
105
  const MessageBubbleContent = (props: IMessageBubbleContentProperties) => {
91
- const { children, raw, ...restProps } = props;
106
+ const { sizing, shape, variant, children, raw, ...restProps } = props;
92
107
  const { id, states } = useMessageBubble();
93
108
 
94
109
  return (
95
110
  <MessageBubbleBadge
96
- variant="secondary"
97
111
  data-raw={Boolean(raw)}
98
112
  data-side={states?.side}
113
+ variant={variant ?? ComponentVariantEnum.Border}
114
+ shape={shape ?? ComponentShapeEnum.Smooth}
115
+ sizing={sizing ?? ComponentSizeEnum.Medium}
99
116
  aria-label={`message-bubble-content-${id}`}
100
117
  {...restProps}
101
118
  >
@@ -133,6 +150,7 @@ const MessageBubbleMeta = (props: IMessageBubbleMetaProperties) => {
133
150
  data-raw={Boolean(raw)}
134
151
  data-side={states?.side}
135
152
  aria-label={`message-bubble-meta-${states?.side}`}
153
+ className="fs-small-60 opacity-default-60"
136
154
  {...restProps}
137
155
  >
138
156
  {formattedDate}
@@ -21,27 +21,24 @@ export const MessageBubbleBadge: React.FC<IBadgeProperties> = styled(Badge)`
21
21
  width: 100%;
22
22
  justify-self: flex-end;
23
23
  padding: var(--measurement-medium-30) var(--measurement-medium-50) !important;
24
- border-radius: var(--measurement-medium-60) !important;
25
24
 
26
25
  &[data-side="left"] {
27
- background-color: var(--contrast-color) !important;
28
- border-bottom-left-radius: 0 !important;
26
+ border-top-left-radius: 0 !important;
29
27
  }
30
28
 
31
29
  &[data-side="right"] {
32
- background-color: var(--font-color-alpha-10) !important;
33
- border-bottom-right-radius: 0 !important;
30
+ border-top-right-radius: 0 !important;
34
31
  }
35
32
  `;
36
33
 
37
34
  export const MessageBubbleContentWrapper = styled.div`
38
- line-height: 1.5;
35
+ line-height: 1.3;
39
36
  font-weight: 500;
40
37
  word-break: keep-all;
41
38
  width: 100%;
42
39
 
43
40
  * {
44
- font-size: var(--fontsize-medium-10) !important;
41
+ font-size: inherit !important;
45
42
  }
46
43
  `;
47
44
 
@@ -1,6 +1,4 @@
1
1
  import React from "react";
2
- import type { Meta, StoryObj } from "@storybook/react";
3
-
4
2
  import { Field, OTPField, Page } from "..";
5
3
 
6
4
  const meta = {
@@ -8,13 +6,17 @@ const meta = {
8
6
  component: OTPField,
9
7
  tags: ["autodocs"],
10
8
  decorators: [
11
- (Story) => (
12
- <div className="m-medium-30">
13
- <Story />
14
- </div>
9
+ (Story: any) => (
10
+ <Page>
11
+ <Page.Content className="p-medium-30">
12
+ <div className="flex flex-column align-center justify-center h-100">
13
+ <Story />
14
+ </div>
15
+ </Page.Content>
16
+ </Page>
15
17
  ),
16
18
  ],
17
- } satisfies Meta<typeof OTPField>;
19
+ };
18
20
  export default meta;
19
21
 
20
22
  export const Default = {
@@ -27,24 +29,20 @@ export const Default = {
27
29
  setValue(value.trim());
28
30
  }, []);
29
31
  return (
30
- <Page>
31
- <Page.Content>
32
- <form aria-label="story-form">
33
- <Field.Wrapper className="w-100">
34
- <Field.Label>Confirmation code</Field.Label>
35
- <OTPField length={6} onComplete={handleComplete}>
36
- <OTPField.Group>
37
- {Array.from({ length: 6 }).map((_, index) => (
38
- <OTPField.Slot key={index} index={index} />
39
- ))}
40
- </OTPField.Group>
41
- </OTPField>
32
+ <form aria-label="story-form" style={{ width: 325 }}>
33
+ <Field.Wrapper className="w-100">
34
+ <Field.Label>Confirmation code</Field.Label>
35
+ <OTPField length={6} onComplete={handleComplete}>
36
+ <OTPField.Group>
37
+ {Array.from({ length: 6 }).map((_, index) => (
38
+ <OTPField.Slot key={index} index={index} />
39
+ ))}
40
+ </OTPField.Group>
41
+ </OTPField>
42
42
 
43
- {value}
44
- </Field.Wrapper>
45
- </form>
46
- </Page.Content>
47
- </Page>
43
+ {value}
44
+ </Field.Wrapper>
45
+ </form>
48
46
  );
49
47
  },
50
48
  };
@@ -6,6 +6,11 @@ import { useOTPField, OTPFieldContext } from "./hooks";
6
6
  import { OTPCell } from "./styles";
7
7
 
8
8
  import type { OTPFieldProps, OTPFieldSlotProps } from "./types";
9
+ import {
10
+ ComponentShapeEnum,
11
+ ComponentSizeEnum,
12
+ ComponentVariantEnum,
13
+ } from "../../../../types";
9
14
 
10
15
  export interface IOTPFieldComposition {
11
16
  Slot: typeof OTPFieldSlot;
@@ -191,6 +196,8 @@ OTPFieldGroup.displayName = "OTPField.Group";
191
196
  */
192
197
  const OTPFieldSlot = ({
193
198
  index,
199
+ shape,
200
+ raw,
194
201
  ...props
195
202
  }: OTPFieldSlotProps & React.InputHTMLAttributes<HTMLInputElement>) => {
196
203
  const context = useOTPField();
@@ -215,6 +222,8 @@ const OTPFieldSlot = ({
215
222
  type="text"
216
223
  data-testid="otp-field-slot"
217
224
  data-active={activeIndex === index}
225
+ data-shape={shape ?? ComponentShapeEnum.Smooth}
226
+ data-raw={Boolean(raw)}
218
227
  autoComplete="one-time-code"
219
228
  maxLength={1}
220
229
  value={otp[index] || ""}
@@ -1,33 +1,131 @@
1
- import styled from "styled-components";
1
+ import styled, { css } from "styled-components";
2
2
 
3
- export const OTPCell = styled.input`
3
+ const OTPShapeStyles = css`
4
+ &[data-shape="square"] {
5
+ border-radius: 0;
6
+ }
7
+ &[data-shape="smooth"] {
8
+ border-radius: var(--measurement-medium-20);
9
+ }
10
+ &[data-shape="round"] {
11
+ border-radius: var(--measurement-large-90);
12
+ padding-left: var(--measurement-medium-50) !important;
13
+ }
14
+ `;
15
+
16
+ const OTPCellDefaultStyles = css`
17
+ outline: none;
18
+ cursor: text;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ text-align: center;
23
+ box-sizing: border-box;
24
+
25
+ font-size: var(--fontsize-medium-20);
26
+
27
+ padding: 0 var(--measurement-medium-30);
4
28
  width: var(--measurement-medium-90);
5
29
  height: var(--measurement-medium-90);
6
- border: var(--measurement-small-10) solid var(--font-color-alpha-10);
7
30
 
8
- border-radius: var(--measurement-medium-30);
31
+ line-height: 1;
32
+ letter-spacing: calc(
33
+ var(--fontsize-small-10) - ((var(--fontsize-small-10) * 1.066))
34
+ );
35
+
36
+ border: var(--measurement-small-10) solid transparent;
37
+
9
38
  backdrop-filter: blur(var(--measurement-small-10));
39
+ color: var(--font-color-alpha-60);
10
40
 
11
- text-align: center;
12
- font-size: var(--fontsize-medium-10);
13
- font-weight: 500;
41
+ transition: all ease-in-out 0.2s;
14
42
 
15
- color: var(--font-color-alpha-10);
16
- text-shadow: 0 0 0 var(--font-color);
43
+ svg,
44
+ span,
45
+ img {
46
+ opacity: 0.6;
47
+ }
48
+
49
+ &:hover,
50
+ &:focus,
51
+ &:active,
52
+ &:focus-within,
53
+ &:has(:active) {
54
+ color: var(--font-color);
55
+ svg,
56
+ span,
57
+ img {
58
+ opacity: 1;
59
+ }
60
+ }
61
+
62
+ &::placeholder {
63
+ color: var(--font-color-alpha-30);
64
+ }
65
+
66
+ &:disabled,
67
+ &:has(:disabled) {
68
+ cursor: not-allowed;
69
+ opacity: 0.6;
70
+ }
17
71
 
18
72
  background-color: transparent;
19
- transition: all 0.2s ease-in-out;
20
- outline: none;
73
+ border-color: var(--font-color-alpha-10);
21
74
 
22
- &:focus:not(:active) {
23
- background-color: var(--font-color-alpha-10);
75
+ &:hover,
76
+ &:focus,
77
+ &:active,
78
+ &:focus-within,
79
+ &:has(:hover),
80
+ &:has(:active) {
81
+ border-color: var(--font-color-alpha-20);
24
82
  }
25
83
 
26
- &:hover:not(:active) {
84
+ &:focus,
85
+ &:active,
86
+ &:focus-within,
87
+ &:has(:active) {
88
+ box-shadow: 0 0 0 var(--measurement-small-30) var(--alpha-accent-30);
89
+ }
90
+
91
+ background-color: transparent;
92
+ border-color: var(--font-color-alpha-10);
93
+
94
+ &:hover,
95
+ &:focus,
96
+ &:active,
97
+ &:focus-within,
98
+ &:has(:hover),
99
+ &:has(:active) {
27
100
  border-color: var(--font-color-alpha-20);
28
101
  }
29
102
 
30
- &::placeholder {
31
- opacity: var(--opacity-default-10);
103
+ &:focus,
104
+ &:active,
105
+ &:focus-within,
106
+ &:has(:active) {
107
+ box-shadow: 0 0 0 var(--measurement-small-30) var(--font-color-alpha-10);
108
+ }
109
+
110
+ &[data-error="true"] {
111
+ color: var(--color-red);
112
+ border-color: var(--alpha-red-10);
113
+
114
+ &:hover,
115
+ &:focus,
116
+ &:active,
117
+ &:focus-within,
118
+ &:has(:hover),
119
+ &:has(:active) {
120
+ background-color: var(--alpha-red-10);
121
+ box-shadow: 0 0 0 var(--measurement-small-30) var(--alpha-red-10);
122
+ }
123
+ }
124
+ `;
125
+
126
+ export const OTPCell = styled.input`
127
+ &[data-raw="false"] {
128
+ ${OTPCellDefaultStyles}
129
+ ${OTPShapeStyles}
32
130
  }
33
131
  `;
@@ -1,3 +1,10 @@
1
+ import {
2
+ IComponentSize,
3
+ IComponentVariant,
4
+ IComponentShape,
5
+ IComponentStyling,
6
+ } from "../../../../../types";
7
+
1
8
  export interface OTPFieldContextType {
2
9
  otp: string[];
3
10
  inputRefs: React.MutableRefObject<(HTMLInputElement | null)[]>;
@@ -18,6 +25,7 @@ export interface OTPFieldProps {
18
25
  onComplete?: (value: string) => void;
19
26
  }
20
27
 
21
- export interface OTPFieldSlotProps {
28
+ export interface OTPFieldSlotProps
29
+ extends IComponentVariant, IComponentStyling, IComponentShape {
22
30
  index: number;
23
31
  }
@@ -19,6 +19,7 @@ export const OverlayWrapper = styled.div<any>`
19
19
 
20
20
  &[data-raw="false"] {
21
21
  background-color: rgba(0, 0, 0, 0.6); // Always forced to black
22
+
22
23
  animation-duration: 0.2s;
23
24
  animation-name: animate-fade;
24
25
  animation-fill-mode: backwards;
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Page } from "../";
4
+ import { Ruler } from "./index";
5
+
6
+ /**
7
+ * RulerCanvas renders a canvas area with horizontal and vertical rulers,
8
+ * allowing users to create and drag guides for precise alignment.
9
+ */
10
+ const meta = {
11
+ title: "Components/Ruler",
12
+ component: Ruler,
13
+ tags: ["autodocs"],
14
+ } satisfies Meta<typeof Ruler>;
15
+ export default meta;
16
+
17
+ type Story = StoryObj<typeof meta>;
18
+
19
+ export const Default: Story = {
20
+ args: {},
21
+ render: ({ ...args }) => (
22
+ <Page>
23
+ <Page.Content>
24
+ <Ruler.Root>
25
+ <Ruler>
26
+ <Ruler.Corner />
27
+ <Ruler.Row orientation="horizontal" />
28
+ <Ruler.Row orientation="vertical" />
29
+ <Ruler.Canvas>
30
+ <Ruler.Lines />
31
+ <div
32
+ className="h-100 w-100 flex align-center justify-center"
33
+ style={{ background: "var(--background-color)" }}
34
+ >
35
+ <p className="fs-medium-20">Canvas Content</p>
36
+ </div>
37
+ </Ruler.Canvas>
38
+ </Ruler>
39
+ </Ruler.Root>
40
+ </Page.Content>
41
+ </Page>
42
+ ),
43
+ };
@@ -0,0 +1,3 @@
1
+ export const RULER_SIZE = 16;
2
+ export const MAJOR_TICK_INTERVAL = 100;
3
+ export const MINOR_TICK_INTERVAL = 10;
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import type { RulerContextType, Guide } from "../types";
5
+
6
+ const RulerContext = React.createContext<RulerContextType | null>(null);
7
+
8
+ export function useRuler() {
9
+ const context = React.useContext(RulerContext);
10
+ if (!context) throw new Error("useRuler must be used within RulerProvider");
11
+ return context;
12
+ }
13
+
14
+ export function RulerProvider({ children }: { children: React.ReactNode }) {
15
+ const [guides, setGuides] = React.useState<Guide[]>([]);
16
+ const [activeGuide, setActiveGuide] = React.useState<Guide | null>(null);
17
+ const [isDragging, setIsDragging] = React.useState(false);
18
+ const canvasRef = React.useRef<HTMLDivElement>(null);
19
+
20
+ const addGuide = React.useCallback((guide: Omit<Guide, "id">) => {
21
+ const newGuide = { ...guide, id: crypto.randomUUID() };
22
+ setGuides((prev) => [...prev, newGuide]);
23
+ return newGuide;
24
+ }, []);
25
+
26
+ const updateGuide = React.useCallback((id: string, position: number) => {
27
+ setGuides((prev) =>
28
+ prev.map((g) => (g.id === id ? { ...g, position } : g)),
29
+ );
30
+ }, []);
31
+
32
+ const removeGuide = React.useCallback((id: string) => {
33
+ setGuides((prev) => prev.filter((g) => g.id !== id));
34
+ }, []);
35
+
36
+ return (
37
+ <RulerContext.Provider
38
+ value={{
39
+ guides,
40
+ addGuide,
41
+ updateGuide,
42
+ removeGuide,
43
+ activeGuide,
44
+ setActiveGuide,
45
+ isDragging,
46
+ setIsDragging,
47
+ canvasRef,
48
+ }}
49
+ >
50
+ {children}
51
+ </RulerContext.Provider>
52
+ );
53
+ }