@krrli/cm-designsystem 1.1.0 → 1.19.7

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 (174) hide show
  1. package/README.md +391 -2
  2. package/dist/components/avatar/Avatar.d.ts +71 -0
  3. package/dist/components/avatar/Avatar.js +63 -0
  4. package/dist/components/branding/BrandingGallery.d.ts +1 -0
  5. package/dist/components/branding/BrandingGallery.js +139 -0
  6. package/dist/components/button/Button.d.ts +54 -0
  7. package/dist/components/button/Button.js +56 -0
  8. package/dist/components/button/Button.test.d.ts +1 -0
  9. package/dist/components/button/Button.test.js +30 -0
  10. package/dist/components/color/ColorDoc.d.ts +4 -0
  11. package/dist/components/color/ColorDoc.js +10 -0
  12. package/dist/components/file-upload/FileUpload.d.ts +83 -0
  13. package/dist/components/file-upload/FileUpload.js +70 -0
  14. package/dist/components/form/Form.d.ts +54 -0
  15. package/dist/components/form/Form.js +38 -0
  16. package/dist/components/icon-button/IconButton.d.ts +50 -0
  17. package/dist/components/icon-button/IconButton.js +22 -0
  18. package/dist/components/icon-button/IconButton.test.d.ts +1 -0
  19. package/dist/components/icon-button/IconButton.test.js +22 -0
  20. package/dist/components/icons/IconBase.d.ts +5 -0
  21. package/dist/components/icons/IconBase.js +9 -0
  22. package/dist/components/icons/generated/ArrowDown.d.ts +3 -0
  23. package/dist/components/icons/generated/ArrowDown.js +4 -0
  24. package/dist/components/icons/generated/ArrowLeft.d.ts +3 -0
  25. package/dist/components/icons/generated/ArrowLeft.js +4 -0
  26. package/dist/components/icons/generated/ArrowRight.d.ts +3 -0
  27. package/dist/components/icons/generated/ArrowRight.js +4 -0
  28. package/dist/components/icons/generated/ArrowUp.d.ts +3 -0
  29. package/dist/components/icons/generated/ArrowUp.js +4 -0
  30. package/dist/components/icons/generated/Calendar.d.ts +3 -0
  31. package/dist/components/icons/generated/Calendar.js +4 -0
  32. package/dist/components/icons/generated/Cancel.d.ts +3 -0
  33. package/dist/components/icons/generated/Cancel.js +4 -0
  34. package/dist/components/icons/generated/Checkmark.d.ts +3 -0
  35. package/dist/components/icons/generated/Checkmark.js +4 -0
  36. package/dist/components/icons/generated/Edit.d.ts +3 -0
  37. package/dist/components/icons/generated/Edit.js +4 -0
  38. package/dist/components/icons/generated/Eye.d.ts +3 -0
  39. package/dist/components/icons/generated/Eye.js +4 -0
  40. package/dist/components/icons/generated/Fullscreen.d.ts +3 -0
  41. package/dist/components/icons/generated/Fullscreen.js +4 -0
  42. package/dist/components/icons/generated/HeartFilled.d.ts +3 -0
  43. package/dist/components/icons/generated/HeartFilled.js +4 -0
  44. package/dist/components/icons/generated/HeartOutline.d.ts +3 -0
  45. package/dist/components/icons/generated/HeartOutline.js +4 -0
  46. package/dist/components/icons/generated/Location.d.ts +3 -0
  47. package/dist/components/icons/generated/Location.js +4 -0
  48. package/dist/components/icons/generated/LogOut.d.ts +3 -0
  49. package/dist/components/icons/generated/LogOut.js +4 -0
  50. package/dist/components/icons/generated/Mumble.d.ts +3 -0
  51. package/dist/components/icons/generated/Mumble.js +4 -0
  52. package/dist/components/icons/generated/Profile.d.ts +3 -0
  53. package/dist/components/icons/generated/Profile.js +4 -0
  54. package/dist/components/icons/generated/ReplyFilled.d.ts +3 -0
  55. package/dist/components/icons/generated/ReplyFilled.js +4 -0
  56. package/dist/components/icons/generated/ReplyOutline.d.ts +3 -0
  57. package/dist/components/icons/generated/ReplyOutline.js +4 -0
  58. package/dist/components/icons/generated/Repost.d.ts +3 -0
  59. package/dist/components/icons/generated/Repost.js +4 -0
  60. package/dist/components/icons/generated/Send.d.ts +3 -0
  61. package/dist/components/icons/generated/Send.js +4 -0
  62. package/dist/components/icons/generated/Settings.d.ts +3 -0
  63. package/dist/components/icons/generated/Settings.js +4 -0
  64. package/dist/components/icons/generated/Share.d.ts +3 -0
  65. package/dist/components/icons/generated/Share.js +4 -0
  66. package/dist/components/icons/generated/Time.d.ts +3 -0
  67. package/dist/components/icons/generated/Time.js +4 -0
  68. package/dist/components/icons/generated/Upload.d.ts +3 -0
  69. package/dist/components/icons/generated/Upload.js +4 -0
  70. package/dist/components/icons/generated/index.d.ts +24 -0
  71. package/dist/components/icons/generated/index.js +24 -0
  72. package/dist/components/index.d.ts +25 -0
  73. package/dist/components/index.js +25 -0
  74. package/dist/components/input/Input.d.ts +61 -0
  75. package/dist/components/input/Input.js +47 -0
  76. package/dist/components/like-toggle/LikeToggle.d.ts +97 -0
  77. package/dist/components/like-toggle/LikeToggle.js +185 -0
  78. package/dist/components/like-toggle/LikeToggle.test.d.ts +1 -0
  79. package/dist/components/like-toggle/LikeToggle.test.js +35 -0
  80. package/dist/components/modal/Modal.d.ts +75 -0
  81. package/dist/components/modal/Modal.js +63 -0
  82. package/dist/components/modal/Modal.test.d.ts +1 -0
  83. package/dist/components/modal/Modal.test.js +24 -0
  84. package/dist/components/navi-button/NaviButton.d.ts +26 -0
  85. package/dist/components/navi-button/NaviButton.js +29 -0
  86. package/dist/components/navi-button/NaviButton.test.d.ts +1 -0
  87. package/dist/components/navi-button/NaviButton.test.js +22 -0
  88. package/dist/components/navi-user-button/NaviUserButton.d.ts +26 -0
  89. package/dist/components/navi-user-button/NaviUserButton.js +29 -0
  90. package/dist/components/round-button/RoundButton.d.ts +25 -0
  91. package/dist/components/round-button/RoundButton.js +28 -0
  92. package/dist/components/round-button/RoundButton.test.d.ts +1 -0
  93. package/dist/components/round-button/RoundButton.test.js +21 -0
  94. package/dist/components/tabs/TabItem.d.ts +11 -0
  95. package/dist/components/tabs/TabItem.js +13 -0
  96. package/dist/components/tabs/Tabs.d.ts +67 -0
  97. package/dist/components/tabs/Tabs.js +67 -0
  98. package/dist/components/tabs/Tabs.test.d.ts +1 -0
  99. package/dist/components/tabs/Tabs.test.js +61 -0
  100. package/dist/components/text-link/TextLink.d.ts +9 -0
  101. package/dist/components/text-link/TextLink.js +15 -0
  102. package/dist/components/text-link/TextLink.test.d.ts +1 -0
  103. package/dist/components/text-link/TextLink.test.js +14 -0
  104. package/dist/components/textarea/Textarea.d.ts +48 -0
  105. package/dist/components/textarea/Textarea.js +46 -0
  106. package/dist/components/timed-button/TimedButton.d.ts +56 -0
  107. package/dist/components/timed-button/TimedButton.js +106 -0
  108. package/dist/components/timed-button/TimedButton.test.d.ts +1 -0
  109. package/dist/components/timed-button/TimedButton.test.js +35 -0
  110. package/dist/components/toggle/Toggle.d.ts +62 -0
  111. package/dist/components/toggle/Toggle.js +67 -0
  112. package/dist/components/toggle/Toggle.test.d.ts +1 -0
  113. package/dist/components/toggle/Toggle.test.js +93 -0
  114. package/dist/components/typography/Heading.d.ts +17 -0
  115. package/dist/components/typography/Heading.js +11 -0
  116. package/dist/components/typography/Label.d.ts +15 -0
  117. package/dist/components/typography/Label.js +7 -0
  118. package/dist/components/typography/Paragraph.d.ts +15 -0
  119. package/dist/components/typography/Paragraph.js +7 -0
  120. package/dist/components/typography/Placeholder.d.ts +13 -0
  121. package/dist/components/typography/Placeholder.js +7 -0
  122. package/dist/components/typography/ValidationMessage.d.ts +15 -0
  123. package/dist/components/typography/ValidationMessage.js +9 -0
  124. package/dist/components/typography/styles.d.ts +74 -0
  125. package/dist/components/typography/styles.js +52 -0
  126. package/dist/favicon.svg +18 -0
  127. package/dist/index.d.ts +1 -0
  128. package/dist/index.es.js +7550 -0
  129. package/dist/index.js +2 -0
  130. package/dist/logo-inline-gradient.svg +43 -0
  131. package/dist/setupTests.d.ts +1 -0
  132. package/dist/setupTests.js +7 -0
  133. package/package.json +78 -33
  134. package/.github/CODEOWNERS +0 -7
  135. package/.github/semantic.yml +0 -24
  136. package/.github/workflows/publish-npm.yml +0 -29
  137. package/.github/workflows/storybook.yml +0 -44
  138. package/.releaserc.json +0 -9
  139. package/.storybook/main.ts +0 -19
  140. package/.storybook/preview.ts +0 -21
  141. package/.storybook/vitest.setup.ts +0 -7
  142. package/src/index.ts +0 -4
  143. package/stories/Button.stories.ts +0 -54
  144. package/stories/Button.tsx +0 -37
  145. package/stories/Button2.stories.ts +0 -54
  146. package/stories/Button2.tsx +0 -41
  147. package/stories/Configure.mdx +0 -364
  148. package/stories/Header.stories.ts +0 -34
  149. package/stories/Header.tsx +0 -56
  150. package/stories/Page.stories.ts +0 -33
  151. package/stories/Page.tsx +0 -73
  152. package/stories/assets/accessibility.png +0 -0
  153. package/stories/assets/accessibility.svg +0 -1
  154. package/stories/assets/addon-library.png +0 -0
  155. package/stories/assets/assets.png +0 -0
  156. package/stories/assets/avif-test-image.avif +0 -0
  157. package/stories/assets/context.png +0 -0
  158. package/stories/assets/discord.svg +0 -1
  159. package/stories/assets/docs.png +0 -0
  160. package/stories/assets/figma-plugin.png +0 -0
  161. package/stories/assets/github.svg +0 -1
  162. package/stories/assets/share.png +0 -0
  163. package/stories/assets/styling.png +0 -0
  164. package/stories/assets/testing.png +0 -0
  165. package/stories/assets/theming.png +0 -0
  166. package/stories/assets/tutorials.svg +0 -1
  167. package/stories/assets/youtube.svg +0 -1
  168. package/stories/button.css +0 -30
  169. package/stories/button2.css +0 -30
  170. package/stories/header.css +0 -32
  171. package/stories/page.css +0 -68
  172. package/tsconfig.json +0 -13
  173. package/vitest.config.ts +0 -35
  174. package/vitest.shims.d.ts +0 -1
@@ -0,0 +1,67 @@
1
+ import React from "react";
2
+ import { type VariantProps } from "tailwind-variants";
3
+ import type { TabItemProps } from "./TabItem";
4
+ declare const tabStyles: import("tailwind-variants").TVReturnType<{
5
+ selected: {
6
+ false: {
7
+ trigger: string[];
8
+ };
9
+ true: {
10
+ trigger: string[];
11
+ };
12
+ };
13
+ effect: {
14
+ first: {};
15
+ middle: {};
16
+ last: {};
17
+ };
18
+ }, {
19
+ list: string[];
20
+ trigger: string[];
21
+ }, undefined, {
22
+ selected: {
23
+ false: {
24
+ trigger: string[];
25
+ };
26
+ true: {
27
+ trigger: string[];
28
+ };
29
+ };
30
+ effect: {
31
+ first: {};
32
+ middle: {};
33
+ last: {};
34
+ };
35
+ }, {
36
+ list: string[];
37
+ trigger: string[];
38
+ }, import("tailwind-variants").TVReturnType<{
39
+ selected: {
40
+ false: {
41
+ trigger: string[];
42
+ };
43
+ true: {
44
+ trigger: string[];
45
+ };
46
+ };
47
+ effect: {
48
+ first: {};
49
+ middle: {};
50
+ last: {};
51
+ };
52
+ }, {
53
+ list: string[];
54
+ trigger: string[];
55
+ }, undefined, unknown, unknown, undefined>>;
56
+ type TabVariants = VariantProps<typeof tabStyles>;
57
+ export interface TabLabelProps {
58
+ value: string;
59
+ label: string;
60
+ }
61
+ export interface TabProps extends TabVariants {
62
+ value: string;
63
+ onChange?: (value: string) => void;
64
+ children: React.ReactElement<TabItemProps>[];
65
+ }
66
+ export declare const Tabs: (props: TabProps) => import("react/jsx-runtime").JSX.Element;
67
+ export {};
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as RadixTabs from "@radix-ui/react-tabs";
3
+ import React from "react";
4
+ import { tv } from "tailwind-variants";
5
+ import { Label } from "../typography/Label";
6
+ const tabStyles = tv({
7
+ slots: {
8
+ list: ["bg-slate-200", "rounded-lg", "p-1", "flex", "gap-2", "group"],
9
+ trigger: ["pt-2", "pb-2", "pr-3", "pl-3", "rounded-md"],
10
+ },
11
+ variants: {
12
+ selected: {
13
+ false: {
14
+ trigger: ["bg-slate-200", "group-hover:text-slate-800"],
15
+ },
16
+ true: {
17
+ trigger: ["bg-white", "text-violet-600"],
18
+ },
19
+ },
20
+ effect: {
21
+ first: {},
22
+ middle: {},
23
+ last: {},
24
+ },
25
+ },
26
+ compoundVariants: [
27
+ {
28
+ selected: true,
29
+ effect: "first",
30
+ class: {
31
+ trigger: ["group-hover:pr-6"],
32
+ },
33
+ },
34
+ {
35
+ selected: true,
36
+ effect: "middle",
37
+ class: {
38
+ trigger: ["group-hover:pr-6", "group-hover:pl-6"],
39
+ },
40
+ },
41
+ {
42
+ selected: true,
43
+ effect: "last",
44
+ class: {
45
+ trigger: ["group-hover:pl-6"],
46
+ },
47
+ },
48
+ ],
49
+ });
50
+ export const Tabs = (props) => {
51
+ const { list, trigger } = tabStyles(props);
52
+ const [currentSelection, setSelection] = React.useState(props.value);
53
+ const getEffectVariant = (index, max) => index === 0 ? "first" : max === index ? "last" : "middle";
54
+ const onClick = (value) => {
55
+ setSelection(value);
56
+ props.onChange?.(value);
57
+ };
58
+ return (_jsxs(RadixTabs.Root, { defaultValue: props.value, children: [_jsx(RadixTabs.List, { className: list(), children: props.children.map((child, index) => (_jsx(RadixTabs.Trigger, { value: child.props.value, className: trigger({
59
+ selected: child.props.value === currentSelection,
60
+ effect: getEffectVariant(index, props.children.length - 1),
61
+ }), onClick: () => onClick(child.props.value), children: _jsx(Label, { size: "lg", children: child.props.label }) }, child.props.value))) }), React.Children.map(props.children, (child, index) => {
62
+ if (React.isValidElement(child)) {
63
+ return React.cloneElement(child, { key: props.value || index });
64
+ }
65
+ return child;
66
+ })] }));
67
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import { describe, expect, test, vi } from "vitest";
4
+ import { TabItem } from "./TabItem";
5
+ import { Tabs } from "./Tabs";
6
+ describe("Tabs", () => {
7
+ test("should render three tabs", async () => {
8
+ // Arrange
9
+ render(_jsxs(Tabs, { value: "1", children: [_jsx(TabItem, { label: "tab1", value: "1", children: "tabItem1" }), _jsx(TabItem, { label: "tab2", value: "2", children: "tabItem2" }), _jsx(TabItem, { label: "tab3", value: "3", children: "tabItem3" })] }));
10
+ // Assert
11
+ expect(screen.getByRole("tab", { name: /tab1/i })).toBeVisible();
12
+ expect(screen.getByRole("tab", { name: /tab2/i })).toBeVisible();
13
+ expect(screen.getByRole("tab", { name: /tab3/i })).toBeVisible();
14
+ const tabItem1 = screen.getByRole("tabpanel", {
15
+ name: /tab1/i,
16
+ });
17
+ expect(tabItem1).toBeVisible();
18
+ expect(tabItem1).toHaveTextContent(/tabItem1/i);
19
+ const tabItems = screen
20
+ .getAllByRole("tabpanel", { hidden: true })
21
+ .filter((el) => el.hasAttribute("hidden"));
22
+ expect(tabItems.length).toBe(2);
23
+ });
24
+ test("should render first tab as selected when value = 1 is set", async () => {
25
+ // Arrange
26
+ render(_jsxs(Tabs, { value: "1", children: [_jsx(TabItem, { label: "tab1", value: "1", children: "tabItem1" }), _jsx(TabItem, { label: "tab2", value: "2", children: "tabItem2" }), _jsx(TabItem, { label: "tab3", value: "3", children: "tabItem3" })] }));
27
+ // Assert
28
+ const tabItem1 = screen.getByRole("tabpanel", {
29
+ name: /tab1/i,
30
+ });
31
+ expect(tabItem1).toBeVisible();
32
+ expect(tabItem1).toHaveTextContent(/tabItem1/i);
33
+ expect(tabItem1).not.toHaveAttribute("hidden");
34
+ const tabItems = screen
35
+ .getAllByRole("tabpanel", { hidden: true })
36
+ .filter((el) => el.hasAttribute("hidden"));
37
+ expect(tabItems.length).toBe(2);
38
+ });
39
+ test("should render second tab as selected when value = 2 is set", async () => {
40
+ // Arrange
41
+ render(_jsxs(Tabs, { value: "2", children: [_jsx(TabItem, { label: "tab1", value: "1", children: "tabItem1" }), _jsx(TabItem, { label: "tab2", value: "2", children: "tabItem2" }), _jsx(TabItem, { label: "tab3", value: "3", children: "tabItem3" })] }));
42
+ // Assert
43
+ const tabItem2 = screen.getByRole("tabpanel", {
44
+ name: /tab2/i,
45
+ });
46
+ expect(tabItem2).toBeVisible();
47
+ expect(tabItem2).toHaveTextContent(/tabItem2/i);
48
+ expect(tabItem2).not.toHaveAttribute("hidden");
49
+ const tabItems = screen
50
+ .getAllByRole("tabpanel", { hidden: true })
51
+ .filter((el) => el.hasAttribute("hidden"));
52
+ expect(tabItems.length).toBe(2);
53
+ });
54
+ test("should call onChange when second tab clicked", () => {
55
+ // Arrange
56
+ const onValueChange = vi.fn();
57
+ render(_jsxs(Tabs, { value: "2", onChange: onValueChange, children: [_jsx(TabItem, { label: "tab1", value: "1", children: "tabItem1" }), _jsx(TabItem, { label: "tab2", value: "2", children: "tabItem2" }), _jsx(TabItem, { label: "tab3", value: "3", children: "tabItem3" })] }));
58
+ fireEvent.click(screen.getByRole("tab", { name: /tab2/i }));
59
+ expect(onValueChange).toHaveBeenCalledWith("2");
60
+ });
61
+ });
@@ -0,0 +1,9 @@
1
+ import { type VariantProps } from "tailwind-variants";
2
+ declare const textLinkStyles: import("tailwind-variants").TVReturnType<{} | {} | {}, undefined, string[], {} | {}, undefined, import("tailwind-variants").TVReturnType<unknown, undefined, string[], unknown, unknown, undefined>>;
3
+ type TextLinkVariants = VariantProps<typeof textLinkStyles>;
4
+ interface TextLinkProps extends TextLinkVariants {
5
+ href: string;
6
+ children: React.ReactNode;
7
+ }
8
+ export declare const TextLink: (props: TextLinkProps) => import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { tv } from "tailwind-variants";
3
+ import { Label } from "../typography/Label";
4
+ const textLinkStyles = tv({
5
+ base: [
6
+ "text-violet-600",
7
+ "underline",
8
+ "decoration-violet-600",
9
+ "underline-offset-2",
10
+ "hover:decoration-violet-200",
11
+ ],
12
+ });
13
+ export const TextLink = (props) => {
14
+ return (_jsx(Label, { size: "sm", as: "span", children: _jsx("a", { href: props.href, className: textLinkStyles(props), children: props.children }) }));
15
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, expect, test } from "vitest";
4
+ import { TextLink } from "./TextLink";
5
+ describe("Text Link", () => {
6
+ test("should render three tabs", async () => {
7
+ // Arrange
8
+ render(_jsx(TextLink, { href: "https://example.com", children: "Example Link" }));
9
+ // Assert
10
+ const link = screen.getByRole("link");
11
+ expect(link).toBeVisible();
12
+ expect(link).toHaveAttribute("href", "https://example.com");
13
+ });
14
+ });
@@ -0,0 +1,48 @@
1
+ import { type VariantProps } from "tailwind-variants";
2
+ declare const textareaStyles: import("tailwind-variants").TVReturnType<{
3
+ [key: string]: {
4
+ [key: string]: import("tailwind-merge").ClassNameValue | {
5
+ base?: import("tailwind-merge").ClassNameValue;
6
+ control?: import("tailwind-merge").ClassNameValue;
7
+ message?: import("tailwind-merge").ClassNameValue;
8
+ };
9
+ };
10
+ } | {
11
+ [x: string]: {
12
+ [x: string]: import("tailwind-merge").ClassNameValue | {
13
+ base?: import("tailwind-merge").ClassNameValue;
14
+ control?: import("tailwind-merge").ClassNameValue;
15
+ message?: import("tailwind-merge").ClassNameValue;
16
+ };
17
+ };
18
+ } | {}, {
19
+ base: string[];
20
+ control: string[];
21
+ message: string[];
22
+ }, undefined, {
23
+ [key: string]: {
24
+ [key: string]: import("tailwind-merge").ClassNameValue | {
25
+ base?: import("tailwind-merge").ClassNameValue;
26
+ control?: import("tailwind-merge").ClassNameValue;
27
+ message?: import("tailwind-merge").ClassNameValue;
28
+ };
29
+ };
30
+ } | {}, {
31
+ base: string[];
32
+ control: string[];
33
+ message: string[];
34
+ }, import("tailwind-variants").TVReturnType<unknown, {
35
+ base: string[];
36
+ control: string[];
37
+ message: string[];
38
+ }, undefined, unknown, unknown, undefined>>;
39
+ type TextareaVariants = VariantProps<typeof textareaStyles>;
40
+ interface TextareaProps extends TextareaVariants {
41
+ name: string;
42
+ placeholder: string;
43
+ label?: string;
44
+ isRequired?: boolean;
45
+ onChange: (value: string) => void;
46
+ }
47
+ export declare const Textarea: ({ isRequired, ...props }: TextareaProps) => import("react/jsx-runtime").JSX.Element;
48
+ export {};
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as RadixForm from "@radix-ui/react-form";
3
+ import { useState } from "react";
4
+ import { tv } from "tailwind-variants";
5
+ import { Label } from "../typography/Label";
6
+ import { ValidationMessage } from "../typography/ValidationMessage";
7
+ const textareaStyles = tv({
8
+ slots: {
9
+ base: ["flex", "flex-col", "gap-1"],
10
+ control: [
11
+ "w-full",
12
+ "ring",
13
+ "hover:ring-2",
14
+ "focus:ring-2",
15
+ "ring-slate-200",
16
+ "hover:ring-slate-300",
17
+ "focus:ring-violet-600",
18
+ "focus-visible:ring-violet-600",
19
+ "focus-within:ring-violet-600",
20
+ "focus-visible:outline-none",
21
+ "bg-slate-100",
22
+ "rounded-lg",
23
+ "p-4",
24
+ "transition",
25
+ "duration-300",
26
+ "ease-in-out",
27
+ "font-medium",
28
+ "text-slate-900",
29
+ "text-[20px]/[135%]",
30
+ "tracking-normal",
31
+ "placeholder:font-medium",
32
+ "placeholder:text-slate-500",
33
+ "placeholder:text-[20px]/[135%]",
34
+ "placeholder:tracking-normal",
35
+ "resize-none",
36
+ ],
37
+ message: ["text-error"],
38
+ },
39
+ });
40
+ export const Textarea = ({ isRequired = false, ...props }) => {
41
+ const { base, control, message } = textareaStyles({
42
+ ...props,
43
+ });
44
+ const [value, setValue] = useState("");
45
+ return (_jsxs(RadixForm.Field, { name: props.name, className: base(props), children: [props.label && (_jsx(RadixForm.Label, { children: _jsx(Label, { size: "md", children: props.label }) })), _jsx("div", { children: _jsx(RadixForm.Control, { asChild: true, children: _jsx("textarea", { className: control(props), required: isRequired, placeholder: props.placeholder, onChange: (e) => setValue(e.target.value), onBlur: () => props.onChange(value) }) }) }), _jsx("div", { className: message(props), children: isRequired && (_jsx(RadixForm.Message, { match: "valueMissing", children: _jsx(ValidationMessage, { children: `${props.name} is required` }) })) })] }));
46
+ };
@@ -0,0 +1,56 @@
1
+ import { type VariantProps } from "tailwind-variants";
2
+ declare const timedButtonStyles: import("tailwind-variants").TVReturnType<{
3
+ pressed: {
4
+ false: {};
5
+ true: {
6
+ base: string[];
7
+ };
8
+ };
9
+ animating: {
10
+ true: {};
11
+ false: {};
12
+ };
13
+ }, {
14
+ base: string[];
15
+ icon: string[];
16
+ label: string[];
17
+ }, undefined, {
18
+ pressed: {
19
+ false: {};
20
+ true: {
21
+ base: string[];
22
+ };
23
+ };
24
+ animating: {
25
+ true: {};
26
+ false: {};
27
+ };
28
+ }, {
29
+ base: string[];
30
+ icon: string[];
31
+ label: string[];
32
+ }, import("tailwind-variants").TVReturnType<{
33
+ pressed: {
34
+ false: {};
35
+ true: {
36
+ base: string[];
37
+ };
38
+ };
39
+ animating: {
40
+ true: {};
41
+ false: {};
42
+ };
43
+ }, {
44
+ base: string[];
45
+ icon: string[];
46
+ label: string[];
47
+ }, undefined, unknown, unknown, undefined>>;
48
+ type TimedButtonVariants = VariantProps<typeof timedButtonStyles>;
49
+ interface TimedButtonProps extends TimedButtonVariants {
50
+ onClick: () => void;
51
+ icon?: React.ReactNode;
52
+ label?: string;
53
+ labelActive?: string;
54
+ }
55
+ export declare const TimedButton: ({ onClick, icon, label, labelActive, }: TimedButtonProps) => import("react/jsx-runtime").JSX.Element;
56
+ export {};
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { tv } from "tailwind-variants";
4
+ import { Share } from "../icons/generated";
5
+ import { Label } from "../typography/Label";
6
+ const timedButtonStyles = tv({
7
+ slots: {
8
+ base: [
9
+ "inline-flex",
10
+ "items-center",
11
+ "justify-center",
12
+ "gap-2",
13
+ "h-8",
14
+ "px-3",
15
+ "py-2",
16
+ "rounded-full",
17
+ "focus-visible:outline-none",
18
+ "focus-visible:ring-2",
19
+ "focus-visible:ring-offset-2",
20
+ "disabled:opacity-50",
21
+ "disabled:pointer-events-none",
22
+ "hover:bg-slate-100",
23
+ "overflow-hidden",
24
+ ],
25
+ icon: ["inline-flex"],
26
+ label: ["transition-opacity", "duration-350", "ease-in-out"],
27
+ },
28
+ variants: {
29
+ pressed: {
30
+ false: {},
31
+ true: {
32
+ base: ["bg-slate-100"],
33
+ },
34
+ },
35
+ animating: {
36
+ true: {},
37
+ false: {},
38
+ },
39
+ },
40
+ compoundVariants: [
41
+ {
42
+ pressed: true,
43
+ animating: true,
44
+ class: {
45
+ label: ["opacity-0"],
46
+ },
47
+ },
48
+ {
49
+ pressed: true,
50
+ animating: false,
51
+ class: {
52
+ label: ["opacity-100"],
53
+ },
54
+ },
55
+ {
56
+ pressed: false,
57
+ animating: true,
58
+ class: {
59
+ label: ["opacity-0"],
60
+ },
61
+ },
62
+ {
63
+ pressed: false,
64
+ animating: false,
65
+ class: {
66
+ label: ["opacity-100"],
67
+ },
68
+ },
69
+ ],
70
+ defaultVariants: {
71
+ pressed: false,
72
+ animating: false,
73
+ },
74
+ });
75
+ export const TimedButton = ({ onClick, icon = _jsx(Share, {}), label = "Copy Link", labelActive = "Link copied", }) => {
76
+ const [pressed, setPressed] = useState(false);
77
+ const [animating, setAnimating] = useState(false);
78
+ const timeoutsRef = useRef([]);
79
+ const { base, icon: iconClass, label: labelClass, } = timedButtonStyles({ pressed, animating });
80
+ useEffect(() => {
81
+ // Cleanup all timeouts when component unmounts
82
+ return () => {
83
+ timeoutsRef.current.forEach((timeout) => clearTimeout(timeout));
84
+ timeoutsRef.current = [];
85
+ };
86
+ }, []);
87
+ const handleClick = () => {
88
+ // Klick -> Pressed
89
+ setPressed(true);
90
+ setAnimating(true);
91
+ onClick();
92
+ // Hover -> Pressed: Dissolve über 350ms
93
+ const timeout1 = setTimeout(() => setAnimating(false), 350);
94
+ timeoutsRef.current.push(timeout1);
95
+ // Nach 1s Verzögerung -> zurück zu Default
96
+ const timeout2 = setTimeout(() => {
97
+ setPressed(false);
98
+ setAnimating(true);
99
+ // Dissolve zurück
100
+ const timeout3 = setTimeout(() => setAnimating(false), 350);
101
+ timeoutsRef.current.push(timeout3);
102
+ }, 1000);
103
+ timeoutsRef.current.push(timeout2);
104
+ };
105
+ return (_jsxs("button", { className: base(), onClick: handleClick, children: [_jsx("span", { className: iconClass(), "aria-hidden": "true", "aria-label": "Share", children: icon }), _jsx("span", { className: labelClass(), children: _jsx(Label, { as: "span", size: "md", children: pressed ? labelActive : label }) })] }));
106
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+ import { describe, expect, test, vi } from "vitest";
4
+ import { TimedButton } from "./TimedButton";
5
+ describe("TimedButton", () => {
6
+ test("should render icon and label", async () => {
7
+ // Arrange
8
+ render(_jsx(TimedButton, { onClick: function () { }, label: "Copy Link", labelActive: "Link Copied" }));
9
+ const button = screen.getByRole("button");
10
+ expect(button).toBeVisible();
11
+ expect(button).toHaveTextContent("Copy Link");
12
+ // Icon prüfen
13
+ expect(screen.getByLabelText("Share")).toBeInTheDocument();
14
+ });
15
+ test("renders initial state and toggles correctly", async () => {
16
+ // Arrange
17
+ const onClick = vi.fn();
18
+ render(_jsx(TimedButton, { onClick: onClick, label: "Copy Link", labelActive: "Link Copied" }));
19
+ const button = screen.getByRole("button");
20
+ expect(button).toBeVisible();
21
+ // Initial state with Likes
22
+ expect(button).toHaveTextContent("Copy Link");
23
+ expect(screen.getByLabelText("Share")).toBeInTheDocument();
24
+ // Simulate click
25
+ fireEvent.click(button);
26
+ // Immediate state after click: "Liked" label + HeartFilled
27
+ expect(button).toHaveTextContent("Link Copied");
28
+ expect(screen.getByLabelText("Share")).toBeInTheDocument();
29
+ expect(onClick).toHaveBeenCalled();
30
+ // Wait for animation + likes count update (1s delay + 350ms animation)
31
+ await waitFor(() => {
32
+ expect(button).toHaveTextContent("Copy Link");
33
+ }, { timeout: 1500 });
34
+ });
35
+ });
@@ -0,0 +1,62 @@
1
+ import { type VariantProps } from "tailwind-variants";
2
+ declare const toggleStyles: import("tailwind-variants").TVReturnType<{
3
+ pressed: {
4
+ true: {
5
+ base: string[];
6
+ icon: string[];
7
+ label: string[];
8
+ };
9
+ false: {
10
+ base: string[];
11
+ icon: string[];
12
+ label: string[];
13
+ };
14
+ };
15
+ }, {
16
+ base: string[];
17
+ icon: string[];
18
+ label: never[];
19
+ }, undefined, {
20
+ pressed: {
21
+ true: {
22
+ base: string[];
23
+ icon: string[];
24
+ label: string[];
25
+ };
26
+ false: {
27
+ base: string[];
28
+ icon: string[];
29
+ label: string[];
30
+ };
31
+ };
32
+ }, {
33
+ base: string[];
34
+ icon: string[];
35
+ label: never[];
36
+ }, import("tailwind-variants").TVReturnType<{
37
+ pressed: {
38
+ true: {
39
+ base: string[];
40
+ icon: string[];
41
+ label: string[];
42
+ };
43
+ false: {
44
+ base: string[];
45
+ icon: string[];
46
+ label: string[];
47
+ };
48
+ };
49
+ }, {
50
+ base: string[];
51
+ icon: string[];
52
+ label: never[];
53
+ }, undefined, unknown, unknown, undefined>>;
54
+ type ToggleVariants = VariantProps<typeof toggleStyles>;
55
+ interface ToggleProps extends ToggleVariants {
56
+ ariaLabel: string;
57
+ labelText: string;
58
+ pressed?: boolean;
59
+ onToggle: (toggled: boolean) => void;
60
+ }
61
+ export declare const Toggle: ({ ariaLabel, labelText, pressed, onToggle, }: ToggleProps) => import("react/jsx-runtime").JSX.Element;
62
+ export {};
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as RadixToggle from "@radix-ui/react-toggle";
3
+ import { tv } from "tailwind-variants";
4
+ import { ReplyFilled, ReplyOutline } from "../icons/generated";
5
+ import { Label } from "../typography/Label";
6
+ const toggleStyles = tv({
7
+ slots: {
8
+ base: [
9
+ "inline-flex",
10
+ "items-center",
11
+ "justify-center",
12
+ "gap-2",
13
+ "h-8",
14
+ "px-3",
15
+ "py-2",
16
+ "rounded-full",
17
+ "transition-all",
18
+ "duration-150",
19
+ "ease-in-out",
20
+ "focus-visible:outline-none",
21
+ "focus-visible:ring-2",
22
+ "focus-visible:ring-offset-2",
23
+ "disabled:opacity-50",
24
+ "disabled:pointer-events-none",
25
+ ],
26
+ icon: ["inline-flex"],
27
+ label: [],
28
+ },
29
+ variants: {
30
+ pressed: {
31
+ true: {
32
+ base: ["hover:bg-violet-50", "hover:text-violet-600"],
33
+ icon: [
34
+ "text-violet-600",
35
+ "hover:transition-all",
36
+ "hover:duration-300",
37
+ "hover:ease-out",
38
+ ],
39
+ label: [
40
+ "text-inherit",
41
+ "hover:transition-all",
42
+ "hover:duration-300",
43
+ "hover:ease-out",
44
+ ],
45
+ },
46
+ false: {
47
+ base: ["hover:bg-violet-50", "hover:text-violet-600"],
48
+ icon: [
49
+ "text-inherit",
50
+ "hover:transition-all",
51
+ "hover:duration-350",
52
+ "hover:ease-in-out",
53
+ ],
54
+ label: [
55
+ "text-inherit",
56
+ "hover:transition-all",
57
+ "hover:duration-350",
58
+ "hover:ease-in-out",
59
+ ],
60
+ },
61
+ },
62
+ },
63
+ });
64
+ export const Toggle = ({ ariaLabel, labelText, pressed = false, onToggle, }) => {
65
+ const { base, icon, label: labelSlot } = toggleStyles({ pressed });
66
+ return (_jsxs(RadixToggle.Root, { "aria-label": ariaLabel, className: base(), pressed: pressed, onPressedChange: onToggle, children: [_jsx("span", { className: icon(), "aria-hidden": "true", "aria-label": pressed ? "ReplyFilled" : "ReplyOutline", children: pressed ? _jsx(ReplyFilled, {}) : _jsx(ReplyOutline, {}) }), _jsx("span", { className: labelSlot(), children: _jsx(Label, { as: "span", size: "md", children: labelText }) })] }));
67
+ };
@@ -0,0 +1 @@
1
+ export {};