@usefui/components 1.5.1 → 1.5.3

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 (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.d.mts +237 -9
  3. package/dist/index.d.ts +237 -9
  4. package/dist/index.js +1095 -311
  5. package/dist/index.mjs +1049 -278
  6. package/package.json +6 -6
  7. package/src/avatar/index.tsx +1 -1
  8. package/src/badge/index.tsx +3 -3
  9. package/src/breadcrumb/Breadcrumb.stories.tsx +36 -0
  10. package/src/breadcrumb/index.tsx +117 -0
  11. package/src/breadcrumb/styles/index.ts +19 -0
  12. package/src/button/Button.stories.tsx +80 -8
  13. package/src/button/index.tsx +67 -5
  14. package/src/button/styles/index.ts +82 -10
  15. package/src/card/Card.stories.tsx +57 -0
  16. package/src/card/index.tsx +55 -0
  17. package/src/card/styles/index.ts +72 -0
  18. package/src/copy-button/CopyButton.stories.tsx +29 -0
  19. package/src/copy-button/index.tsx +101 -0
  20. package/src/dialog/index.tsx +1 -1
  21. package/src/dropdown/index.tsx +1 -1
  22. package/src/field/Field.stories.tsx +39 -8
  23. package/src/field/index.tsx +5 -0
  24. package/src/field/styles/index.ts +38 -12
  25. package/src/index.ts +8 -0
  26. package/src/page/index.tsx +1 -1
  27. package/src/privacy-field/PrivacyField.stories.tsx +29 -0
  28. package/src/privacy-field/index.tsx +56 -0
  29. package/src/privacy-field/styles/index.ts +17 -0
  30. package/src/resizable/Resizable.stories.tsx +40 -0
  31. package/src/resizable/index.tsx +108 -0
  32. package/src/resizable/styles/index.ts +65 -0
  33. package/src/skeleton/Skeleton.stories.tsx +32 -0
  34. package/src/skeleton/index.tsx +43 -0
  35. package/src/skeleton/styles/index.ts +56 -0
  36. package/src/spinner/Spinner.stories.tsx +27 -0
  37. package/src/spinner/index.tsx +19 -0
  38. package/src/spinner/styles/index.ts +43 -0
  39. package/src/text-area/Textarea.stories.tsx +32 -0
  40. package/src/text-area/index.tsx +78 -0
  41. package/src/text-area/styles/index.ts +84 -0
  42. package/src/tooltip/index.tsx +4 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefui/components",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Open Source React components library",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,16 +24,16 @@
24
24
  "styled-components": "^5.1.34"
25
25
  },
26
26
  "devDependencies": {
27
- "@types/react": "^18.3.23",
27
+ "@types/react": "^18.3.26",
28
28
  "@types/react-dom": "^18.3.7",
29
29
  "@types/styled-components": "^5.1.34",
30
30
  "react": "^18.3.1",
31
31
  "react-dom": "^18.3.1",
32
32
  "styled-components": "^5.3.11",
33
- "typescript": "^5.9.2",
34
- "@usefui/core": "^1.3.3",
35
- "@usefui/hooks": "^1.3.3",
36
- "@usefui/tokens": "^1.5.2"
33
+ "typescript": "^5.9.3",
34
+ "@usefui/core": "^1.3.4",
35
+ "@usefui/hooks": "^1.3.4",
36
+ "@usefui/tokens": "^1.5.4"
37
37
  },
38
38
  "scripts": {
39
39
  "test": "vitest run --coverage --logHeapUsage",
@@ -34,7 +34,7 @@ export interface IAvatarProperties
34
34
  *
35
35
  * @param {IAvatarProperties} props - The props for the Avatar component.
36
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.
37
+ * @param {ComponentSizeEnum} props.sizing - The size of the avatar. Defaults to "medium".
38
38
  * @param {string} props.status - The status of the user represented by the avatar.
39
39
  * @param {string} props.src - The source URL of the image to be displayed in the avatar.
40
40
  * @param {string} props.alt - The alternative text for the image in the avatar.
@@ -3,7 +3,7 @@
3
3
  import React from "react";
4
4
  import { BadgeWrapper } from "./styles";
5
5
 
6
- import { IComponentStyling } from "../../../../types";
6
+ import type { IComponentStyling, TComponentShape } from "../../../../types";
7
7
 
8
8
  interface IBadgeProperties
9
9
  extends IComponentStyling,
@@ -16,7 +16,7 @@ interface IBadgeProperties
16
16
  | "warning"
17
17
  | "success"
18
18
  | "meta";
19
- shape?: "square" | "smooth" | "round";
19
+ shape?: TComponentShape;
20
20
  }
21
21
 
22
22
  /**
@@ -29,7 +29,7 @@ interface IBadgeProperties
29
29
  *
30
30
  * @param {IBadgeProperties} props - The props for the Badge component.
31
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`.
32
+ * @param {TComponentShape} props.shape - The shape of the component. Defaults to `smooth`.
33
33
  * @param {string} props.variant - The style definition used by the component.
34
34
  * @param {ReactNode} props.children - The content to be rendered inside the Badge.
35
35
  * @returns {ReactElement} The Badge component.
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { Page, Breadcrumb } from "..";
3
+
4
+ import type { Meta, StoryObj } from "@storybook/react";
5
+
6
+ /**
7
+ *
8
+ */
9
+ const meta = {
10
+ title: "Components/Breadcrumb",
11
+ component: Breadcrumb,
12
+ tags: ["autodocs"],
13
+ } satisfies Meta<typeof Breadcrumb>;
14
+ export default meta;
15
+
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ const navigate = (href: string) => {
19
+ console.log(href);
20
+ };
21
+ export const Default: Story = {
22
+ args: {
23
+ path: "",
24
+ },
25
+ render: ({ ...args }) => (
26
+ <Page>
27
+ <Page.Content>
28
+ <div className="flex align-center justify-center h-100">
29
+ <Breadcrumb path={"/test/demo-path/current-path"} navigate={navigate}>
30
+ <Breadcrumb.Item>Home</Breadcrumb.Item>
31
+ </Breadcrumb>
32
+ </div>
33
+ </Page.Content>
34
+ </Page>
35
+ ),
36
+ };
@@ -0,0 +1,117 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+
5
+ import { ItemWrapper, SeparatorItem } from "./styles";
6
+ import { Button, type IButtonProperties } from "../";
7
+
8
+ const SEGMENT_SEPARATOR = "/";
9
+
10
+ interface BreadcrumbProps extends React.ComponentProps<"div"> {
11
+ navigate?: (href: string) => void;
12
+ capitalizeItems?: boolean;
13
+ path: string;
14
+ }
15
+ interface BreadcrumbItemProps extends IButtonProperties {
16
+ isLastItem?: boolean;
17
+ }
18
+ export interface BreadcrumbComposition {
19
+ Item: typeof BreadcrumbItem;
20
+ Separator: typeof BreadcrumbSeparator;
21
+ }
22
+
23
+ /**
24
+ * Breadcrumb Item are used to display the path to the current resource.
25
+ *
26
+ * @param {BreadcrumbItemProps} props - The props for the Breadcrumb Item component.
27
+ * @param {boolean} props.isLastItem - Render the item as latest on the links hierarchy.
28
+ * @returns {ReactElement} The Breadcrumb Item component.
29
+ */
30
+ const BreadcrumbItem = (props: BreadcrumbItemProps) => {
31
+ const { isLastItem = false, children, ...restProps } = props;
32
+
33
+ return (
34
+ <Button sizing="small" variant="ghost" {...restProps}>
35
+ <ItemWrapper data-current={isLastItem}>{children}</ItemWrapper>
36
+ </Button>
37
+ );
38
+ };
39
+ BreadcrumbItem.displayName = "Breadcrumb.Item";
40
+
41
+ /**
42
+ * Breadcrumb Separators are used to display hierarchy of links.
43
+ *
44
+ * @param {React.ComponentProps<"span">} props - The props for the Breadcrumb Separators component.
45
+ * @returns {ReactElement} The Breadcrumb Separators component.
46
+ */
47
+ const BreadcrumbSeparator = (props: React.ComponentProps<"span">) => {
48
+ const { children } = props;
49
+
50
+ return <SeparatorItem>{children ?? SEGMENT_SEPARATOR}</SeparatorItem>;
51
+ };
52
+ BreadcrumbSeparator.displayName = "Breadcrumb.Separator";
53
+
54
+ /**
55
+ * Breadcrumbs are used to display the path to the current resource using a hierarchy of links.
56
+ *
57
+ * @param {BreadcrumbProps} props - The props for the Breadcrumb component.
58
+ * @param {boolean} props.capitalizeItems - Render the current path with a capitalized case. Defaults to 'true'.
59
+ * @param {string} props.path - The current path.
60
+ * @param {function} props.navigate - The function used to redirect to the path route.
61
+ * @returns {ReactElement} The Breadcrumb component.
62
+ */
63
+ const Breadcrumb = (props: BreadcrumbProps) => {
64
+ const { path, navigate, capitalizeItems = true, children } = props;
65
+
66
+ const segments = path.split(SEGMENT_SEPARATOR).filter(Boolean);
67
+ const items = segments.map((segment, index) => {
68
+ const href = `/${segments.slice(0, index + 1).join(SEGMENT_SEPARATOR)}`;
69
+ const isLastItem = index === segments.length - 1;
70
+
71
+ return {
72
+ href,
73
+ label: segment,
74
+ isLastItem,
75
+ };
76
+ });
77
+
78
+ const breadcrumbLabel = (segment: string) => {
79
+ return capitalizeItems
80
+ ? segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, " ")
81
+ : segment.replace(/-/g, " ");
82
+ };
83
+
84
+ const handleNavigate = React.useCallback((href: string) => {
85
+ if (navigate) navigate(href);
86
+ }, []);
87
+
88
+ if (path === SEGMENT_SEPARATOR) return null;
89
+ return (
90
+ <div className="flex align-center g-medium-30">
91
+ {children && (
92
+ <React.Fragment>
93
+ {children}
94
+ <Breadcrumb.Separator />
95
+ </React.Fragment>
96
+ )}
97
+ {items.map((item, key) => (
98
+ <React.Fragment key={key}>
99
+ <Breadcrumb.Item
100
+ isLastItem={item.isLastItem}
101
+ disabled={!navigate}
102
+ onClick={() => handleNavigate(item.href)}
103
+ >
104
+ {breadcrumbLabel(item.label)}
105
+ </Breadcrumb.Item>
106
+ {!item.isLastItem && <Breadcrumb.Separator />}
107
+ </React.Fragment>
108
+ ))}
109
+ </div>
110
+ );
111
+ };
112
+ Breadcrumb.displayName = "Breadcrumb";
113
+
114
+ Breadcrumb.Item = BreadcrumbItem;
115
+ Breadcrumb.Separator = BreadcrumbSeparator;
116
+
117
+ export { Breadcrumb, BreadcrumbItem, BreadcrumbSeparator };
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import styled from "styled-components";
4
+
5
+ export const ItemWrapper = styled.span`
6
+ width: 100%;
7
+ line-clamp: 2;
8
+ word-break: break-all;
9
+
10
+ &[data-current="true"] {
11
+ opacity: 1 !important;
12
+ }
13
+ `;
14
+
15
+ export const SeparatorItem = styled.span`
16
+ color: var(--font-color-alpha-10) !important;
17
+ user-select: none;
18
+ pointer-events: none;
19
+ `;
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
- import { Button } from "..";
3
+ import { Button, Page } from "..";
4
4
 
5
5
  const meta = {
6
6
  title: "Components/Button",
@@ -31,17 +31,89 @@ export const Sizes: Story = {
31
31
  );
32
32
  },
33
33
  };
34
- export const Variants: Story = {
34
+ export const Shapes: Story = {
35
35
  render: ({ ...args }) => {
36
36
  return (
37
37
  <div className="flex g-medium-30">
38
- <Button variant="primary">Primary</Button>
39
- <Button variant="secondary">Secondary</Button>
40
- <Button variant="tertiary">Tertiary</Button>
41
- <Button variant="mono">Mono</Button>
42
- <Button variant="border">Border</Button>
43
- <Button variant="ghost">Ghost</Button>
38
+ <Button shape="square">Square</Button>
39
+ <Button shape="smooth">Smooth</Button>
40
+ <Button shape="round">Round</Button>
44
41
  </div>
45
42
  );
46
43
  },
47
44
  };
45
+ export const Variants: Story = {
46
+ render: ({ ...args }) => {
47
+ return (
48
+ <Page>
49
+ <Page.Content className="flex align-center justify-center">
50
+ <div className="flex align-center justify-center g-medium-30 flex-wrap">
51
+ <Button variant="primary">Primary</Button>
52
+ <Button variant="secondary">Secondary</Button>
53
+ <Button variant="tertiary">Tertiary</Button>
54
+ <Button variant="danger">Danger</Button>
55
+ <Button variant="warning">Warning</Button>
56
+ <Button variant="mono">Mono</Button>
57
+ <Button variant="border">Border</Button>
58
+ <Button variant="ghost">Ghost</Button>
59
+ </div>
60
+ </Page.Content>
61
+ </Page>
62
+ );
63
+ },
64
+ };
65
+ export const Animations: Story = {
66
+ render: ({ ...args }) => {
67
+ return (
68
+ <Page>
69
+ <Page.Content className="flex flex-column g-medium-30 align-center justify-center">
70
+ <div className="flex align-center justify-center g-medium-30 flex-wrap">
71
+ <Button animation="reflective" variant="primary">
72
+ Primary
73
+ </Button>
74
+ <Button animation="reflective" variant="secondary">
75
+ Secondary
76
+ </Button>
77
+ <Button animation="reflective" variant="tertiary">
78
+ Tertiary
79
+ </Button>
80
+ <Button animation="reflective" variant="danger">
81
+ Danger
82
+ </Button>
83
+ <Button animation="reflective" variant="warning">
84
+ Warning
85
+ </Button>
86
+ <Button animation="reflective" variant="mono">
87
+ Mono
88
+ </Button>
89
+ <Button animation="reflective" variant="border">
90
+ Border
91
+ </Button>
92
+ </div>
93
+ <div className="flex align-center justify-center g-medium-30 flex-wrap">
94
+ <Button animation="reflective" variant="border" sizing="small">
95
+ Small
96
+ </Button>
97
+ <Button animation="reflective" variant="border" sizing="medium">
98
+ Medium
99
+ </Button>
100
+ <Button animation="reflective" variant="border" sizing="large">
101
+ Large
102
+ </Button>
103
+ </div>
104
+ <div className="flex align-center justify-center g-medium-30 flex-wrap">
105
+ <Button animation="reflective" variant="border" shape="square">
106
+ Square
107
+ </Button>
108
+ <Button animation="reflective" variant="border" shape="smooth">
109
+ Smooth
110
+ </Button>
111
+ <Button animation="reflective" variant="border" shape="round">
112
+ Round
113
+ </Button>
114
+ </div>
115
+ </Page.Content>
116
+ </Page>
117
+ );
118
+ },
119
+ };
@@ -1,21 +1,25 @@
1
1
  "use client";
2
2
 
3
3
  import React from "react";
4
- import { ButtonWrapper } from "./styles";
4
+
5
+ import { ButtonWrapper, ButtonMaskElement, ButtonOverlay } from "./styles";
5
6
  import {
6
7
  IComponentStyling,
7
8
  ComponentSizeEnum,
8
9
  IComponentSize,
9
10
  ComponentVariantEnum,
10
- IComponentVariant,
11
+ TComponentVariant,
12
+ TComponentShape,
11
13
  } from "../../../../types";
12
14
 
13
15
  export interface IButtonProperties
14
16
  extends IComponentStyling,
15
17
  IComponentSize,
16
- IComponentVariant,
17
18
  React.ComponentPropsWithRef<"button"> {
18
19
  rawicon?: boolean;
20
+ variant?: TComponentVariant | "danger" | "warning";
21
+ shape?: TComponentShape;
22
+ animation?: "reflective";
19
23
  }
20
24
 
21
25
  /**
@@ -29,8 +33,10 @@ export interface IButtonProperties
29
33
  * @param {IButtonProperties} props - The props for the Button component.
30
34
  * @param {boolean} props.raw - Define whether the component is styled or not.
31
35
  * @param {boolean} props.rawicon - Define whether the component is styles its svg children.
32
- * @param {ComponentSizeEnum} props.sizing - The size of the component. Defaults to `medium`.
36
+ * @param {ComponentSizeEnum} props.sizing - The size of the component. Defaults to "medium".
37
+ * @param {TComponentShape} props.shape - The size of the component. Defaults to `smooth`.
33
38
  * @param {string} props.variant - The style definition used by the component.
39
+ * @param {string} props.animation - The animation that comes with the variant.
34
40
  * @param {ReactNode} props.children - The content to be rendered inside the button.
35
41
  * @returns {ReactElement} The Button component.
36
42
  */
@@ -38,10 +44,15 @@ export const Button = React.forwardRef<HTMLButtonElement, IButtonProperties>(
38
44
  (props, forwardedRef): React.ReactElement => {
39
45
  const {
40
46
  name,
41
- variant = ComponentVariantEnum.Mono,
47
+ variant = ComponentVariantEnum.Primary,
42
48
  sizing = ComponentSizeEnum.Medium,
49
+ shape = "smooth",
50
+ animation,
43
51
  raw,
44
52
  rawicon,
53
+ onMouseMove,
54
+ onMouseEnter,
55
+ onMouseLeave,
45
56
  children,
46
57
  ...restProps
47
58
  } = props;
@@ -55,6 +66,43 @@ export const Button = React.forwardRef<HTMLButtonElement, IButtonProperties>(
55
66
  const buttonStateDescription = `disabled:${disabledState}`;
56
67
  const ButtonFullDesc = `${buttonDescription}/${buttonStateDescription}`;
57
68
 
69
+ const isReflective = animation === "reflective" && variant !== "ghost";
70
+
71
+ const [position, setPosition] = React.useState({ x: 0, y: 0 });
72
+ const [isHovering, setIsHovering] = React.useState(false);
73
+
74
+ const handleMouseMove = React.useCallback(
75
+ (e: React.MouseEvent<HTMLButtonElement>) => {
76
+ if (onMouseMove) onMouseMove(e);
77
+ if (!isReflective) return;
78
+
79
+ const rect = e.currentTarget.getBoundingClientRect();
80
+ setPosition({
81
+ x: ((e.clientX - rect.left) / rect.width) * 100,
82
+ y: ((e.clientY - rect.top) / rect.height) * 100,
83
+ });
84
+ },
85
+ []
86
+ );
87
+ const handleMouseEnter = React.useCallback(
88
+ (e: React.MouseEvent<HTMLButtonElement>) => {
89
+ if (onMouseEnter) onMouseEnter(e);
90
+ if (!isReflective) return;
91
+
92
+ setIsHovering(true);
93
+ },
94
+ []
95
+ );
96
+ const handleMouseLeave = React.useCallback(
97
+ (e: React.MouseEvent<HTMLButtonElement>) => {
98
+ if (onMouseLeave) onMouseLeave(e);
99
+ if (!isReflective) return;
100
+
101
+ setIsHovering(false);
102
+ },
103
+ []
104
+ );
105
+
58
106
  return (
59
107
  <ButtonWrapper
60
108
  ref={forwardedRef}
@@ -66,11 +114,25 @@ export const Button = React.forwardRef<HTMLButtonElement, IButtonProperties>(
66
114
  aria-disabled={disabledState}
67
115
  data-variant={variant}
68
116
  data-size={sizing}
117
+ data-shape={shape}
118
+ data-animation={animation}
69
119
  data-raw={Boolean(raw)}
70
120
  data-rawicon={Boolean(rawicon)}
71
121
  tabIndex={0}
122
+ onMouseMove={handleMouseMove}
123
+ onMouseEnter={handleMouseEnter}
124
+ onMouseLeave={handleMouseLeave}
72
125
  {...restProps}
73
126
  >
127
+ {isReflective && (
128
+ <ButtonOverlay $isHovering={isHovering} data-shape={shape}>
129
+ <ButtonMaskElement
130
+ $mouseX={position.x}
131
+ $mouseY={position.y}
132
+ data-shape={shape}
133
+ />
134
+ </ButtonOverlay>
135
+ )}
74
136
  {children}
75
137
  </ButtonWrapper>
76
138
  );
@@ -1,5 +1,14 @@
1
1
  import styled, { css } from "styled-components";
2
2
 
3
+ interface OverlayProps {
4
+ $isHovering: boolean;
5
+ }
6
+
7
+ interface MaskProps {
8
+ $mouseX: number;
9
+ $mouseY: number;
10
+ }
11
+
3
12
  const ButtonDefaultStyles = css`
4
13
  cursor: pointer;
5
14
  position: relative;
@@ -26,6 +35,11 @@ const ButtonDefaultStyles = css`
26
35
  opacity: 0.6;
27
36
  }
28
37
 
38
+ svg,
39
+ span {
40
+ transition: all 0.2s ease-in-out;
41
+ }
42
+
29
43
  &:hover,
30
44
  &:focus,
31
45
  &:active {
@@ -78,19 +92,19 @@ const ButtonVariantsStyles = css`
78
92
  }
79
93
  &[data-variant="secondary"] {
80
94
  color: var(--font-color-alpha-60);
81
- background-color: var(--body-color);
95
+ background-color: transparent;
82
96
  border-color: var(--font-color-alpha-10);
83
97
 
84
98
  &:hover,
85
99
  &:focus,
86
100
  &:active {
87
101
  color: var(--font-color);
88
- border-color: var(--font-color-alpha-30);
102
+ background-color: var(--font-color-alpha-10);
103
+ border-color: transparent;
89
104
  }
90
105
  }
91
106
  &[data-variant="tertiary"] {
92
- color: var(--font-color-alpha-60);
93
- border-color: var(--font-color-alpha-10);
107
+ color: var(--font-color-alpha-80);
94
108
  background-color: transparent;
95
109
 
96
110
  &:hover,
@@ -98,7 +112,6 @@ const ButtonVariantsStyles = css`
98
112
  &:active {
99
113
  color: var(--font-color);
100
114
  background-color: var(--font-color-alpha-10);
101
- border-color: transparent;
102
115
  }
103
116
  }
104
117
  &[data-variant="border"] {
@@ -110,6 +123,27 @@ const ButtonVariantsStyles = css`
110
123
  &:focus,
111
124
  &:active {
112
125
  color: var(--font-color);
126
+ border-color: var(--font-color-alpha-20);
127
+ }
128
+ }
129
+ &[data-variant="danger"] {
130
+ color: var(--color-mono-white);
131
+ background-color: var(--color-red);
132
+
133
+ &:hover,
134
+ &:focus,
135
+ &:active {
136
+ background-color: var(--shade-red-10);
137
+ }
138
+ }
139
+ &[data-variant="warning"] {
140
+ color: var(--color-mono-dark);
141
+ background-color: var(--color-orange);
142
+
143
+ &:hover,
144
+ &:focus,
145
+ &:active {
146
+ background-color: var(--shade-orange-10);
113
147
  }
114
148
  }
115
149
  &[data-variant="mono"] {
@@ -140,8 +174,7 @@ const ButtonVariantsStyles = css`
140
174
  `;
141
175
  const ButtonSizeStyles = css`
142
176
  &[data-size="small"] {
143
- border-radius: var(--measurement-medium-20);
144
- font-size: var(--fontsize-medium-10);
177
+ font-size: var(--fontsize-small-60);
145
178
 
146
179
  gap: var(--measurement-medium-10);
147
180
  padding: var(--measurement-medium-10) var(--measurement-medium-30);
@@ -154,27 +187,66 @@ const ButtonSizeStyles = css`
154
187
  }
155
188
  }
156
189
  &[data-size="medium"] {
157
- border-radius: var(--measurement-medium-30);
158
190
  padding: var(--measurement-medium-10) var(--measurement-medium-60);
159
191
  min-width: var(--measurement-medium-90);
160
192
  min-height: var(--measurement-medium-80);
161
193
  }
162
194
  &[data-size="large"] {
163
- border-radius: var(--measurement-medium-30);
164
195
  padding: var(--measurement-medium-10) var(--measurement-medium-60);
165
196
  min-width: var(--measurement-medium-90);
166
197
  min-height: var(--measurement-medium-90);
167
198
  }
168
199
  `;
200
+ const ButtonShapeStyles = css`
201
+ &[data-shape="square"] {
202
+ border-radius: 0;
203
+ }
204
+ &[data-shape="smooth"] {
205
+ border-radius: var(--measurement-medium-20);
206
+ }
207
+ &[data-shape="round"] {
208
+ border-radius: var(--measurement-large-90);
209
+ }
210
+ `;
169
211
 
170
212
  export const ButtonWrapper = styled.button`
171
213
  &[data-raw="false"] {
172
214
  ${ButtonDefaultStyles}
173
215
  ${ButtonSizeStyles}
216
+ ${ButtonShapeStyles}
174
217
  ${ButtonVariantsStyles}
175
-
176
218
  &[data-rawIcon="false"] {
177
219
  ${ButtonIconStyles}
178
220
  }
179
221
  }
180
222
  `;
223
+ export const ButtonOverlay = styled.div<OverlayProps>`
224
+ position: absolute;
225
+ inset: -1px;
226
+ pointer-events: none;
227
+ opacity: ${(props) => (props.$isHovering ? 1 : 0)};
228
+ background: transparent;
229
+ transition: opacity 0.2s ease-in-out;
230
+
231
+ ${ButtonShapeStyles}
232
+ `;
233
+ export const ButtonMaskElement = styled.div<MaskProps>`
234
+ position: absolute;
235
+ inset: 0;
236
+ background: transparent;
237
+ border: var(--measurement-small-10) solid var(--font-color-alpha-20);
238
+ clip-path: inset(0 round var(--measurement-medium-30));
239
+
240
+ mask-image: radial-gradient(
241
+ circle at ${(props) => props.$mouseX}% ${(props) => props.$mouseY}%,
242
+ var(--body-color),
243
+ transparent 100%
244
+ );
245
+ -webkit-mask-image: radial-gradient(
246
+ circle at ${(props) => props.$mouseX}% ${(props) => props.$mouseY}%,
247
+ var(--body-color),
248
+ transparent 100%
249
+ );
250
+
251
+ ${ButtonShapeStyles}
252
+ `;
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+ import { Page, Card, Button } from "..";
3
+
4
+ import type { Meta } from "@storybook/react";
5
+
6
+ /**
7
+ *
8
+ */
9
+ const meta = {
10
+ title: "Components/Card",
11
+ component: Card,
12
+ tags: ["autodocs"],
13
+ } satisfies Meta<typeof Card>;
14
+ export default meta;
15
+
16
+ export const Default = {
17
+ render: () => (
18
+ <Page>
19
+ <Page.Content className="p-medium-60">
20
+ <Card.Grid sizing="medium">
21
+ <Card>
22
+ <Card.Meta>
23
+ <p className="fs-medium-10 opacity-default-30">Header</p>
24
+ </Card.Meta>
25
+ <Card.Body>
26
+ <header className="flex align-start justify-between g-medium-30 w-100">
27
+ <div>
28
+ <p className="fs-medium-30">Title</p>
29
+ <p className="fs-medium-20 opacity-default-30">
30
+ Lorem ipsum dolor sit amet consectetur.
31
+ </p>
32
+ </div>
33
+ <div>
34
+ <Button variant="border" sizing="small">
35
+ Action
36
+ </Button>
37
+ </div>
38
+ </header>
39
+
40
+ <p className="fs-medium-20">
41
+ Lorem ipsum dolor sit amet consectetur, adipisicing elit.
42
+ Repellat a corporis exercitationem.
43
+ </p>
44
+
45
+ <div className="w-100 grid g-medium-10">
46
+ <Button sizing="large">Primary</Button>
47
+ </div>
48
+ </Card.Body>
49
+ <Card.Meta>
50
+ <p className="fs-medium-10 opacity-default-30">Footer</p>
51
+ </Card.Meta>
52
+ </Card>
53
+ </Card.Grid>
54
+ </Page.Content>
55
+ </Page>
56
+ ),
57
+ };