@usefui/components 1.5.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 (104) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -0
  4. package/babel.config.js +12 -0
  5. package/dist/index.d.mts +1299 -0
  6. package/dist/index.d.ts +1299 -0
  7. package/dist/index.js +3701 -0
  8. package/dist/index.mjs +3586 -0
  9. package/package.json +44 -0
  10. package/src/__tests__/Accordion.test.tsx +106 -0
  11. package/src/__tests__/Avatar.test.tsx +89 -0
  12. package/src/__tests__/Badge.test.tsx +58 -0
  13. package/src/__tests__/Button.test.tsx +88 -0
  14. package/src/__tests__/Checkbox.test.tsx +106 -0
  15. package/src/__tests__/Collapsible.test.tsx +79 -0
  16. package/src/__tests__/Dialog.test.tsx +109 -0
  17. package/src/__tests__/Dropdown.test.tsx +159 -0
  18. package/src/__tests__/Field.test.tsx +100 -0
  19. package/src/__tests__/OTPField.test.tsx +199 -0
  20. package/src/__tests__/Overlay.test.tsx +70 -0
  21. package/src/__tests__/Page.test.tsx +98 -0
  22. package/src/__tests__/Portal.test.tsx +28 -0
  23. package/src/__tests__/Sheet.test.tsx +125 -0
  24. package/src/__tests__/Switch.test.tsx +90 -0
  25. package/src/__tests__/Tabs.test.tsx +129 -0
  26. package/src/__tests__/Toggle.test.tsx +67 -0
  27. package/src/__tests__/Toolbar.test.tsx +147 -0
  28. package/src/__tests__/Tooltip.test.tsx +88 -0
  29. package/src/accordion/Accordion.stories.tsx +89 -0
  30. package/src/accordion/hooks/index.tsx +39 -0
  31. package/src/accordion/index.tsx +170 -0
  32. package/src/avatar/Avatar.stories.tsx +62 -0
  33. package/src/avatar/index.tsx +90 -0
  34. package/src/avatar/styles/index.ts +79 -0
  35. package/src/badge/Badge.stories.tsx +60 -0
  36. package/src/badge/index.tsx +58 -0
  37. package/src/badge/styles/index.ts +109 -0
  38. package/src/button/Button.stories.tsx +47 -0
  39. package/src/button/index.tsx +79 -0
  40. package/src/button/styles/index.ts +180 -0
  41. package/src/checkbox/Checkbox.stories.tsx +100 -0
  42. package/src/checkbox/hooks/index.tsx +40 -0
  43. package/src/checkbox/index.tsx +147 -0
  44. package/src/checkbox/styles/index.ts +139 -0
  45. package/src/collapsible/Collapsible.stories.tsx +95 -0
  46. package/src/collapsible/hooks/index.tsx +50 -0
  47. package/src/collapsible/index.tsx +137 -0
  48. package/src/dialog/Dialog.stories.tsx +73 -0
  49. package/src/dialog/hooks/index.tsx +35 -0
  50. package/src/dialog/index.tsx +221 -0
  51. package/src/dialog/styles/index.ts +72 -0
  52. package/src/divider/index.ts +10 -0
  53. package/src/dropdown/Dropdown.stories.tsx +100 -0
  54. package/src/dropdown/hooks/index.tsx +64 -0
  55. package/src/dropdown/index.tsx +316 -0
  56. package/src/dropdown/styles/index.ts +90 -0
  57. package/src/field/Field.stories.tsx +146 -0
  58. package/src/field/hooks/index.tsx +28 -0
  59. package/src/field/index.tsx +183 -0
  60. package/src/field/styles/index.ts +166 -0
  61. package/src/index.ts +33 -0
  62. package/src/otp-field/OTPField.stories.tsx +50 -0
  63. package/src/otp-field/hooks/index.tsx +13 -0
  64. package/src/otp-field/index.tsx +234 -0
  65. package/src/otp-field/styles/index.ts +33 -0
  66. package/src/otp-field/types/index.ts +23 -0
  67. package/src/overlay/Overlay.stories.tsx +59 -0
  68. package/src/overlay/index.tsx +58 -0
  69. package/src/overlay/styles/index.ts +26 -0
  70. package/src/page/Page.stories.tsx +85 -0
  71. package/src/page/index.tsx +265 -0
  72. package/src/page/styles/index.ts +59 -0
  73. package/src/portal/Portal.stories.tsx +27 -0
  74. package/src/portal/index.tsx +36 -0
  75. package/src/scrollarea/Scrollarea.stories.tsx +99 -0
  76. package/src/scrollarea/index.tsx +27 -0
  77. package/src/scrollarea/styles/index.ts +71 -0
  78. package/src/sheet/Sheet.stories.tsx +86 -0
  79. package/src/sheet/hooks/index.tsx +47 -0
  80. package/src/sheet/index.tsx +190 -0
  81. package/src/sheet/styles/index.ts +69 -0
  82. package/src/switch/Switch.stories.tsx +96 -0
  83. package/src/switch/hooks/index.tsx +33 -0
  84. package/src/switch/index.tsx +122 -0
  85. package/src/switch/styles/index.ts +118 -0
  86. package/src/table/index.tsx +138 -0
  87. package/src/table/styles/index.ts +48 -0
  88. package/src/tabs/Tabs.stories.tsx +87 -0
  89. package/src/tabs/hooks/index.tsx +35 -0
  90. package/src/tabs/index.tsx +161 -0
  91. package/src/tabs/styles/index.ts +9 -0
  92. package/src/toggle/Toggle.stories.tsx +118 -0
  93. package/src/toggle/index.tsx +55 -0
  94. package/src/toggle/styles/index.ts +0 -0
  95. package/src/toolbar/Toolbar.stories.tsx +89 -0
  96. package/src/toolbar/hooks/index.tsx +35 -0
  97. package/src/toolbar/index.tsx +243 -0
  98. package/src/toolbar/styles/index.ts +129 -0
  99. package/src/tooltip/Tooltip.stories.tsx +60 -0
  100. package/src/tooltip/index.tsx +177 -0
  101. package/src/tooltip/styles/index.ts +38 -0
  102. package/src/utils/index.ts +2 -0
  103. package/tsconfig.json +18 -0
  104. package/vitest.config.ts +16 -0
@@ -0,0 +1,89 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+
4
+ import { Accordion } from "..";
5
+ import { ComponentSizeEnum, ComponentVariantEnum } from "../../../../types";
6
+
7
+ const meta = {
8
+ title: "Components/Accordion",
9
+ component: Accordion,
10
+ tags: ["autodocs"],
11
+ decorators: [
12
+ (Story) => (
13
+ <div className="m-medium-30">
14
+ <Story />
15
+ </div>
16
+ ),
17
+ ],
18
+ } satisfies Meta<typeof Accordion>;
19
+ export default meta;
20
+
21
+ type Story = StoryObj<typeof meta>;
22
+ export const Default = {
23
+ args: {
24
+ value: "",
25
+ },
26
+ argTypes: {
27
+ spacing: {
28
+ options: [
29
+ ComponentSizeEnum.Small,
30
+ ComponentSizeEnum.Medium,
31
+ ComponentSizeEnum.Large,
32
+ ],
33
+ control: { type: "radio" },
34
+ },
35
+ variant: {
36
+ options: [
37
+ ComponentVariantEnum.Primary,
38
+ ComponentVariantEnum.Secondary,
39
+ ComponentVariantEnum.Tertiary,
40
+ ComponentVariantEnum.Mono,
41
+ ComponentVariantEnum.Border,
42
+ ComponentVariantEnum.Ghost,
43
+ ],
44
+ control: { type: "radio" },
45
+ },
46
+ sizing: {
47
+ options: [
48
+ ComponentSizeEnum.Small,
49
+ ComponentSizeEnum.Medium,
50
+ ComponentSizeEnum.Large,
51
+ ],
52
+ control: { type: "radio" },
53
+ },
54
+ },
55
+ render: ({ ...args }) => (
56
+ <Accordion.Root>
57
+ <Accordion>
58
+ <Accordion.Trigger value="demo">🐻‍❄️</Accordion.Trigger>
59
+ <Accordion.Content value="demo">🐻🐻‍❄️🦊🐱🐶</Accordion.Content>
60
+ </Accordion>
61
+ </Accordion.Root>
62
+ ),
63
+ };
64
+ export const DefaultOpen: Story = {
65
+ render: ({ ...args }) => (
66
+ <Accordion.Root>
67
+ <Accordion>
68
+ <Accordion.Trigger value="demo">🐻</Accordion.Trigger>
69
+ <Accordion.Content value="demo" defaultOpen>
70
+ 🐻🐻‍❄️🦊🐱🐶
71
+ </Accordion.Content>
72
+ </Accordion>
73
+ </Accordion.Root>
74
+ ),
75
+ };
76
+ export const Group: Story = {
77
+ render: ({ ...args }) => (
78
+ <React.Fragment>
79
+ {["🐻", "🐻‍❄️", "🦊", "🐱", "🐶"].map((item) => (
80
+ <Accordion.Root key={item}>
81
+ <Accordion className="m-b-medium-30">
82
+ <Accordion.Trigger value={item}>{item}</Accordion.Trigger>
83
+ <Accordion.Content value={item}>{item}</Accordion.Content>
84
+ </Accordion>
85
+ </Accordion.Root>
86
+ ))}
87
+ </React.Fragment>
88
+ ),
89
+ };
@@ -0,0 +1,39 @@
1
+ import React, { useState, createContext, useContext } from "react";
2
+ import { IReactChildren, IComponentAPI } from "../../../../../types";
3
+
4
+ const defaultComponentAPI = {
5
+ id: "",
6
+ states: {},
7
+ methods: {},
8
+ };
9
+ const AccordionContext = createContext<IComponentAPI>(defaultComponentAPI);
10
+ export const useAccordion = () => useContext(AccordionContext);
11
+
12
+ export const AccordionProvider = ({
13
+ children,
14
+ }: IReactChildren): JSX.Element => {
15
+ const context = useAccordionProvider();
16
+
17
+ return (
18
+ <AccordionContext.Provider value={context}>
19
+ {children}
20
+ </AccordionContext.Provider>
21
+ );
22
+ };
23
+
24
+ function useAccordionProvider(): IComponentAPI {
25
+ const [value, setValue] = useState<string | null>(null);
26
+ const accordionId = React.useId();
27
+
28
+ return {
29
+ id: accordionId,
30
+ states: {
31
+ value,
32
+ },
33
+ methods: {
34
+ applyValue: (value: string): string | void => setValue(value),
35
+ getAccordionId: ({ value, type }: Record<string, string>): string =>
36
+ `${accordionId}-${type}-${value}`,
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,170 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { AccordionProvider, useAccordion } from "./hooks";
5
+ import { Button, IButtonProperties } from "../button";
6
+
7
+ import {
8
+ IReactChildren,
9
+ IComponentSpacing,
10
+ ComponentVariantEnum,
11
+ } from "../../../../types";
12
+
13
+ export interface IAccordionComposition {
14
+ Root: typeof AccordionRoot;
15
+ Trigger: typeof AccordionTrigger;
16
+ Content: typeof AccordionContent;
17
+ }
18
+ export interface IAccordionProperties
19
+ extends IComponentSpacing,
20
+ React.ComponentProps<"div"> {}
21
+ export interface IAccordionTriggerProperties extends IButtonProperties {
22
+ value: string;
23
+ }
24
+ export interface IAccordionContentProperties extends IAccordionProperties {
25
+ defaultOpen?: boolean;
26
+ value: string;
27
+ }
28
+
29
+ /**
30
+ * Accordions are used to expand and collapse sections of content.
31
+ *
32
+ * **Best practices:**
33
+ *
34
+ * - Use a clear and descriptive title for each accordion section.
35
+ * - Ensure that the accordion can be operated using only the keyboard.
36
+ * - Ensure that the focus is properly managed when the accordion section is expanded/collapsed.
37
+ *
38
+ * @param {IAccordionProperties} props - The props for the Accordion component.
39
+ * @param {ReactNode} props.children - The content to be rendered inside the accordion.
40
+ * @returns {ReactElement} The Accordion component.
41
+ */
42
+ const Accordion = (props: IAccordionProperties) => {
43
+ const { children, ...restProps } = props;
44
+ const { id } = useAccordion();
45
+
46
+ return (
47
+ <div id={id} {...restProps}>
48
+ {children}
49
+ </div>
50
+ );
51
+ };
52
+ Accordion.displayName = "Accordion";
53
+
54
+ const AccordionRoot = ({ children }: IReactChildren) => {
55
+ return <AccordionProvider>{children}</AccordionProvider>;
56
+ };
57
+ AccordionRoot.displayName = "Accordion.Root";
58
+
59
+ /**
60
+ * Accordion.Trigger is used to triggers the expansion and collapse of the associated Accordion.Content component.
61
+ *
62
+ * **Best practices:**
63
+ *
64
+ * - Use a clear and descriptive title for the trigger that accurately conveys the content of the associated accordion section.
65
+ * - Ensure that the trigger can be operated using only the keyboard.
66
+ * - Ensure that the focus is properly managed when the trigger is activated.
67
+ *
68
+ * @param {IAccordionTriggerProperties} props - The props for the Accordion.Trigger component.
69
+ * @param {string} props.value - The value used to bind the Accordion.Trigger and Accordion.Content components.
70
+ * @param {ReactNode} props.children - The content to be rendered inside the accordion trigger.
71
+ * @returns {ReactElement} The Accordion.Trigger component.
72
+ */
73
+ const AccordionTrigger = (props: IAccordionTriggerProperties) => {
74
+ const { value, disabled, onClick, children, ...restProps } = props;
75
+
76
+ const { states, methods } = useAccordion();
77
+ const { getAccordionId, applyValue } = methods;
78
+
79
+ const hasSameValueAsContext = value === states.value;
80
+ const IdHandler = {
81
+ trigger: getAccordionId && getAccordionId({ value, type: "trigger" }),
82
+ content: getAccordionId && getAccordionId({ value, type: "content" }),
83
+ };
84
+
85
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
86
+ if (!disabled) {
87
+ onClick && onClick(event);
88
+
89
+ if (applyValue) {
90
+ const unchangedValue = hasSameValueAsContext && states.value !== null;
91
+
92
+ if (unchangedValue) applyValue(null);
93
+ else applyValue(value);
94
+ }
95
+ }
96
+ };
97
+
98
+ return (
99
+ <Button
100
+ id={String(IdHandler.trigger)}
101
+ value={value}
102
+ disabled={disabled ?? false}
103
+ onClick={handleClick}
104
+ aria-expanded={hasSameValueAsContext}
105
+ aria-controls={String(IdHandler.content)}
106
+ data-state={hasSameValueAsContext ? "expanded" : "collapsed"}
107
+ variant={props.variant ?? ComponentVariantEnum.Ghost}
108
+ {...restProps}
109
+ >
110
+ {children}
111
+ </Button>
112
+ );
113
+ };
114
+ AccordionTrigger.displayName = "Accordion.Trigger";
115
+
116
+ /**
117
+ * Accordion.Content is used to contains the content of the associated Accordion.Trigger component.
118
+ *
119
+ * **Best practices:**
120
+ *
121
+ * - Ensure that the content is hidden when the associated accordion section is collapsed.
122
+ * - Ensure that the content is properly focused when the associated accordion section is expanded.
123
+ *
124
+ * @param {IAccordionContentProperties} props - The props for the Accordion.Content component.
125
+ * @param {string} props.value - The value used to bind the Accordion.Content and Accordion.Trigger components.
126
+ * @param {boolean} props.defaultOpen - The initial open state of the accordion content. Defaults to false.
127
+ * @param {IComponentSpacing} props.spacing - The value used by the accordion's content spacings.
128
+ * @param {ReactNode} props.children - The content to be rendered inside the accordion.
129
+ * @returns {ReactElement} The Accordion.Content component.
130
+ */
131
+ const AccordionContent = (props: IAccordionContentProperties) => {
132
+ const { defaultOpen = false, value, children, ...restProps } = props;
133
+
134
+ const { states, methods } = useAccordion();
135
+ const { getAccordionId, applyValue } = methods;
136
+
137
+ const hasSameValueAsContext = value === states.value;
138
+ const IdHandler = {
139
+ trigger: getAccordionId && getAccordionId({ value, type: "trigger" }),
140
+ content: getAccordionId && getAccordionId({ value, type: "content" }),
141
+ };
142
+
143
+ React.useEffect(() => {
144
+ if (defaultOpen && !hasSameValueAsContext && applyValue) applyValue(value);
145
+ }, []);
146
+
147
+ return (
148
+ <React.Fragment>
149
+ {hasSameValueAsContext && (
150
+ <div
151
+ role="article"
152
+ id={String(IdHandler.content)}
153
+ aria-labelledby={String(IdHandler.trigger)}
154
+ aria-expanded={hasSameValueAsContext}
155
+ data-value={value}
156
+ {...restProps}
157
+ >
158
+ {children}
159
+ </div>
160
+ )}
161
+ </React.Fragment>
162
+ );
163
+ };
164
+ AccordionContent.displayName = "Accordion.Content";
165
+
166
+ Accordion.Root = AccordionRoot;
167
+ Accordion.Trigger = AccordionTrigger;
168
+ Accordion.Content = AccordionContent;
169
+
170
+ export { Accordion, AccordionRoot, AccordionTrigger, AccordionContent };
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Avatar } from "..";
4
+
5
+ const meta = {
6
+ title: "Components/Avatar",
7
+ component: Avatar,
8
+ tags: ["autodocs"],
9
+ decorators: [
10
+ (Story) => (
11
+ <div className="m-medium-30">
12
+ <Story />
13
+ </div>
14
+ ),
15
+ ],
16
+ } satisfies Meta<typeof Avatar>;
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof meta>;
20
+ export const Default: Story = {
21
+ render: ({ ...args }) => <Avatar {...args} />,
22
+ };
23
+ export const Status: Story = {
24
+ render: ({ ...args }) => (
25
+ <div className="flex g-medium-30">
26
+ <Avatar status="online" {...args} />
27
+ <Avatar status="away" {...args} />
28
+ <Avatar status="busy" {...args} />
29
+ <Avatar status="offline" {...args} />
30
+ <Avatar {...args} />
31
+ </div>
32
+ ),
33
+ };
34
+ export const Sizes: Story = {
35
+ render: ({ ...args }) => (
36
+ <div className="flex g-medium-30">
37
+ <Avatar sizing="large" {...args} />
38
+ <Avatar sizing="medium" {...args} />
39
+ <Avatar sizing="small" {...args} />
40
+ </div>
41
+ ),
42
+ };
43
+ export const Variants: Story = {
44
+ render: ({ ...args }) => (
45
+ <div className="flex g-medium-30">
46
+ <Avatar />
47
+ <Avatar
48
+ alt="foundation-logo"
49
+ src="https://avatars.githubusercontent.com/u/153380498?s=200&v=4"
50
+ />
51
+ <Avatar>
52
+ <b>AZ</b>
53
+ </Avatar>
54
+ <Avatar
55
+ style={{ backgroundColor: "var(--color-purple)" }}
56
+ status="online"
57
+ >
58
+ <small>Acme</small>
59
+ </Avatar>
60
+ </div>
61
+ ),
62
+ };
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { AvatarWrapper, StatusWrapper } from "./styles";
5
+ import {
6
+ IComponentStyling,
7
+ ComponentSizeEnum,
8
+ IComponentSize,
9
+ } from "../../../../types";
10
+
11
+ export enum AvataStatusEnum {
12
+ Online = "online",
13
+ Away = "away",
14
+ Busy = "busy",
15
+ Offline = "offline",
16
+ }
17
+ export interface IAvatarProperties
18
+ extends IComponentStyling,
19
+ IComponentSize,
20
+ React.HTMLAttributes<HTMLDivElement> {
21
+ src?: string;
22
+ alt?: string;
23
+ status?: "online" | "away" | "busy" | "offline";
24
+ }
25
+
26
+ /**
27
+ * Avatar are used to represents a user or an entity on an interface.
28
+ *
29
+ * **Best practices:**
30
+ *
31
+ * - Use the appropriate size to match the context and the importance of the information.
32
+ * - Always provide an `alt` attribute for accessibility when using an image.
33
+ * - Indicate the user's activity status.
34
+ *
35
+ * @param {IAvatarProperties} props - The props for the Avatar component.
36
+ * @param {boolean} props.raw - Whether the avatar is styled or not.
37
+ * @param {ComponentSizeEnum} props.sizing - The size of the avatar. Defaults to ComponentSizeEnum.Medium.
38
+ * @param {string} props.status - The status of the user represented by the avatar.
39
+ * @param {string} props.src - The source URL of the image to be displayed in the avatar.
40
+ * @param {string} props.alt - The alternative text for the image in the avatar.
41
+ * @param {ReactNode} props.children - The content to be rendered inside the avatar.
42
+ * @returns {ReactElement} The Avatar component.
43
+ */
44
+ export const Avatar = (props: IAvatarProperties) => {
45
+ const {
46
+ raw,
47
+ sizing = ComponentSizeEnum.Medium,
48
+ status,
49
+ src,
50
+ alt,
51
+ children,
52
+ ...restProps
53
+ } = props;
54
+ const sizeLabel = sizing ?? ComponentSizeEnum.Medium;
55
+
56
+ return (
57
+ <AvatarWrapper
58
+ data-raw={Boolean(raw)}
59
+ data-size={sizing}
60
+ data-status={status}
61
+ aria-label={props["aria-label"] ?? `${sizeLabel}-user-avatar`}
62
+ {...restProps}
63
+ >
64
+ {!children && src && (
65
+ <img
66
+ aria-label={`${sizeLabel}-user-avatar-image`}
67
+ alt={alt ?? `${sizeLabel}-user-avatar-image`}
68
+ src={src}
69
+ />
70
+ )}
71
+
72
+ {children}
73
+ {status && (
74
+ <StatusWrapper
75
+ role="img"
76
+ aria-label={`${sizing}-user-avatar-status`}
77
+ aria-labelledby="title desc"
78
+ data-status={status}
79
+ height="16"
80
+ width="16"
81
+ >
82
+ <title>{"Activity status"}</title>
83
+ <desc>{status}</desc>
84
+ <circle role="presentation" cx="8" cy="8" r="6" />
85
+ </StatusWrapper>
86
+ )}
87
+ </AvatarWrapper>
88
+ );
89
+ };
90
+ Avatar.displayName = "Avatar";
@@ -0,0 +1,79 @@
1
+ import styled, { css } from "styled-components";
2
+
3
+ const AvatarSizesStyles = css`
4
+ &[data-size="small"] {
5
+ width: var(--measurement-large-10);
6
+ height: var(--measurement-large-10);
7
+ min-width: var(--measurement-large-10);
8
+ min-height: var(--measurement-large-10);
9
+ }
10
+
11
+ &[data-size="medium"] {
12
+ width: var(--measurement-medium-90);
13
+ height: var(--measurement-medium-90);
14
+ min-width: var(--measurement-medium-90);
15
+ min-height: var(--measurement-medium-90);
16
+ }
17
+
18
+ &[data-size="large"] {
19
+ width: var(--measurement-large-20);
20
+ height: var(--measurement-large-20);
21
+ min-width: var(--measurement-large-20);
22
+ min-height: var(--measurement-large-20);
23
+ }
24
+ `;
25
+ const AvatarStatusesStyles = css`
26
+ &[data-status="online"] {
27
+ fill: var(--shade-green-10);
28
+ stroke: var(--shade-green-20);
29
+ }
30
+
31
+ &[data-status="away"] {
32
+ fill: var(--color-yellow);
33
+ stroke: var(--shade-yellow-10);
34
+ }
35
+
36
+ &[data-status="busy"] {
37
+ fill: var(--color-red);
38
+ stroke: var(--shade-red-10);
39
+ }
40
+
41
+ &[data-status="offline"] {
42
+ fill: var(--body-color);
43
+ stroke: var(--font-color-alpha-10);
44
+ }
45
+ `;
46
+
47
+ export const AvatarWrapper = styled.div`
48
+ &[data-raw="false"] {
49
+ position: relative;
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+
54
+ background-color: var(--body-color);
55
+ border-radius: 100%;
56
+
57
+ img {
58
+ width: inherit;
59
+ height: inherit;
60
+ min-width: inherit;
61
+ min-height: inherit;
62
+ border-radius: 100%;
63
+ }
64
+
65
+ ${AvatarSizesStyles}
66
+ }
67
+ `;
68
+ export const StatusWrapper = styled.svg`
69
+ --status-position: calc(
70
+ var(--measurement-medium-10) - (var(--measurement-medium-10) * 2)
71
+ );
72
+
73
+ position: absolute;
74
+ stroke-width: var(--measurement-small-30);
75
+ bottom: var(--status-position);
76
+ right: var(--status-position);
77
+
78
+ ${AvatarStatusesStyles}
79
+ `;
@@ -0,0 +1,60 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Badge } from "./";
4
+
5
+ // Duplicated doc: The JSDoc content isn't rendering on Storybook.
6
+
7
+ /**
8
+ * Badges are used to convey data or states to the users.
9
+ *
10
+ * **Best practices:**
11
+ *
12
+ * - Define the hierarchy of badges with different variants.
13
+ * - Badge label must convey short and understandable information.
14
+ *
15
+ */
16
+
17
+ const meta = {
18
+ title: "Components/Badge",
19
+ component: Badge,
20
+ tags: ["autodocs"],
21
+ decorators: [
22
+ (Story) => (
23
+ <div className="m-medium-30">
24
+ <Story />
25
+ </div>
26
+ ),
27
+ ],
28
+ } satisfies Meta<typeof Badge>;
29
+ export default meta;
30
+
31
+ type Story = StoryObj<typeof meta>;
32
+ export const Default: Story = {
33
+ render: ({ ...args }) => <Badge {...args} />,
34
+ };
35
+ export const Shapes: Story = {
36
+ render: ({ ...args }) => {
37
+ return (
38
+ <div className="flex g-medium-30">
39
+ <Badge shape="square">Square</Badge>
40
+ <Badge shape="smooth">Smooth</Badge>
41
+ <Badge shape="round">Round</Badge>
42
+ </div>
43
+ );
44
+ },
45
+ };
46
+ export const Variants: Story = {
47
+ render: ({ ...args }) => {
48
+ return (
49
+ <div className="flex g-medium-30">
50
+ <Badge variant="primary">Primary</Badge>
51
+ <Badge variant="secondary">Secondary</Badge>
52
+ <Badge variant="border">Border</Badge>
53
+ <Badge variant="meta">Meta</Badge>
54
+ <Badge variant="success">Success</Badge>
55
+ <Badge variant="warning">Warning</Badge>
56
+ <Badge variant="error">Error</Badge>
57
+ </div>
58
+ );
59
+ },
60
+ };
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { BadgeWrapper } from "./styles";
5
+
6
+ import { IComponentStyling } from "../../../../types";
7
+
8
+ interface IBadgeProperties
9
+ extends IComponentStyling,
10
+ React.ComponentProps<"div"> {
11
+ variant?:
12
+ | "primary"
13
+ | "secondary"
14
+ | "border"
15
+ | "error"
16
+ | "warning"
17
+ | "success"
18
+ | "meta";
19
+ shape?: "square" | "smooth" | "round";
20
+ }
21
+
22
+ /**
23
+ * Badges are used to convey data or states to the users.
24
+ *
25
+ * **Best practices:**
26
+ *
27
+ * - Define the hierarchy of badges with different variants.
28
+ * - Badge label must convey short and understandable information.
29
+ *
30
+ * @param {IBadgeProperties} props - The props for the Badge component.
31
+ * @param {boolean} props.raw - Define whether the component is styled or not.
32
+ * @param {ComponentSizeEnum} props.shape - The shape of the component. Defaults to `smooth`.
33
+ * @param {string} props.variant - The style definition used by the component.
34
+ * @param {ReactNode} props.children - The content to be rendered inside the Badge.
35
+ * @returns {ReactElement} The Badge component.
36
+ */
37
+ export const Badge = (props: IBadgeProperties) => {
38
+ const {
39
+ raw = false,
40
+ variant = "primary",
41
+ shape = "smooth",
42
+ children,
43
+ ...restProps
44
+ } = props;
45
+
46
+ return (
47
+ <BadgeWrapper
48
+ data-raw={raw}
49
+ data-variant={variant}
50
+ data-shape={shape}
51
+ {...restProps}
52
+ >
53
+ {children}
54
+ </BadgeWrapper>
55
+ );
56
+ };
57
+
58
+ Badge.displayName = "Badge";