@krrli/cm-designsystem 1.1.0 → 1.19.8
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.
- package/README.md +422 -2
- package/dist/components/avatar/Avatar.d.ts +71 -0
- package/dist/components/avatar/Avatar.js +63 -0
- package/dist/components/branding/BrandingGallery.d.ts +1 -0
- package/dist/components/branding/BrandingGallery.js +139 -0
- package/dist/components/button/Button.d.ts +54 -0
- package/dist/components/button/Button.js +56 -0
- package/dist/components/button/Button.test.d.ts +1 -0
- package/dist/components/button/Button.test.js +30 -0
- package/dist/components/color/ColorDoc.d.ts +4 -0
- package/dist/components/color/ColorDoc.js +10 -0
- package/dist/components/file-upload/FileUpload.d.ts +83 -0
- package/dist/components/file-upload/FileUpload.js +70 -0
- package/dist/components/form/Form.d.ts +54 -0
- package/dist/components/form/Form.js +38 -0
- package/dist/components/icon-button/IconButton.d.ts +50 -0
- package/dist/components/icon-button/IconButton.js +22 -0
- package/dist/components/icon-button/IconButton.test.d.ts +1 -0
- package/dist/components/icon-button/IconButton.test.js +22 -0
- package/dist/components/icons/IconBase.d.ts +5 -0
- package/dist/components/icons/IconBase.js +9 -0
- package/dist/components/icons/generated/ArrowDown.d.ts +3 -0
- package/dist/components/icons/generated/ArrowDown.js +4 -0
- package/dist/components/icons/generated/ArrowLeft.d.ts +3 -0
- package/dist/components/icons/generated/ArrowLeft.js +4 -0
- package/dist/components/icons/generated/ArrowRight.d.ts +3 -0
- package/dist/components/icons/generated/ArrowRight.js +4 -0
- package/dist/components/icons/generated/ArrowUp.d.ts +3 -0
- package/dist/components/icons/generated/ArrowUp.js +4 -0
- package/dist/components/icons/generated/Calendar.d.ts +3 -0
- package/dist/components/icons/generated/Calendar.js +4 -0
- package/dist/components/icons/generated/Cancel.d.ts +3 -0
- package/dist/components/icons/generated/Cancel.js +4 -0
- package/dist/components/icons/generated/Checkmark.d.ts +3 -0
- package/dist/components/icons/generated/Checkmark.js +4 -0
- package/dist/components/icons/generated/Edit.d.ts +3 -0
- package/dist/components/icons/generated/Edit.js +4 -0
- package/dist/components/icons/generated/Eye.d.ts +3 -0
- package/dist/components/icons/generated/Eye.js +4 -0
- package/dist/components/icons/generated/Fullscreen.d.ts +3 -0
- package/dist/components/icons/generated/Fullscreen.js +4 -0
- package/dist/components/icons/generated/HeartFilled.d.ts +3 -0
- package/dist/components/icons/generated/HeartFilled.js +4 -0
- package/dist/components/icons/generated/HeartOutline.d.ts +3 -0
- package/dist/components/icons/generated/HeartOutline.js +4 -0
- package/dist/components/icons/generated/Location.d.ts +3 -0
- package/dist/components/icons/generated/Location.js +4 -0
- package/dist/components/icons/generated/LogOut.d.ts +3 -0
- package/dist/components/icons/generated/LogOut.js +4 -0
- package/dist/components/icons/generated/Mumble.d.ts +3 -0
- package/dist/components/icons/generated/Mumble.js +4 -0
- package/dist/components/icons/generated/Profile.d.ts +3 -0
- package/dist/components/icons/generated/Profile.js +4 -0
- package/dist/components/icons/generated/ReplyFilled.d.ts +3 -0
- package/dist/components/icons/generated/ReplyFilled.js +4 -0
- package/dist/components/icons/generated/ReplyOutline.d.ts +3 -0
- package/dist/components/icons/generated/ReplyOutline.js +4 -0
- package/dist/components/icons/generated/Repost.d.ts +3 -0
- package/dist/components/icons/generated/Repost.js +4 -0
- package/dist/components/icons/generated/Send.d.ts +3 -0
- package/dist/components/icons/generated/Send.js +4 -0
- package/dist/components/icons/generated/Settings.d.ts +3 -0
- package/dist/components/icons/generated/Settings.js +4 -0
- package/dist/components/icons/generated/Share.d.ts +3 -0
- package/dist/components/icons/generated/Share.js +4 -0
- package/dist/components/icons/generated/Time.d.ts +3 -0
- package/dist/components/icons/generated/Time.js +4 -0
- package/dist/components/icons/generated/Upload.d.ts +3 -0
- package/dist/components/icons/generated/Upload.js +4 -0
- package/dist/components/icons/generated/index.d.ts +24 -0
- package/dist/components/icons/generated/index.js +24 -0
- package/dist/components/index.d.ts +25 -0
- package/dist/components/index.js +25 -0
- package/dist/components/input/Input.d.ts +61 -0
- package/dist/components/input/Input.js +47 -0
- package/dist/components/like-toggle/LikeToggle.d.ts +97 -0
- package/dist/components/like-toggle/LikeToggle.js +185 -0
- package/dist/components/like-toggle/LikeToggle.test.d.ts +1 -0
- package/dist/components/like-toggle/LikeToggle.test.js +35 -0
- package/dist/components/modal/Modal.d.ts +75 -0
- package/dist/components/modal/Modal.js +63 -0
- package/dist/components/modal/Modal.test.d.ts +1 -0
- package/dist/components/modal/Modal.test.js +24 -0
- package/dist/components/navi-button/NaviButton.d.ts +26 -0
- package/dist/components/navi-button/NaviButton.js +29 -0
- package/dist/components/navi-button/NaviButton.test.d.ts +1 -0
- package/dist/components/navi-button/NaviButton.test.js +22 -0
- package/dist/components/navi-user-button/NaviUserButton.d.ts +26 -0
- package/dist/components/navi-user-button/NaviUserButton.js +29 -0
- package/dist/components/round-button/RoundButton.d.ts +25 -0
- package/dist/components/round-button/RoundButton.js +28 -0
- package/dist/components/round-button/RoundButton.test.d.ts +1 -0
- package/dist/components/round-button/RoundButton.test.js +21 -0
- package/dist/components/tabs/TabItem.d.ts +11 -0
- package/dist/components/tabs/TabItem.js +13 -0
- package/dist/components/tabs/Tabs.d.ts +67 -0
- package/dist/components/tabs/Tabs.js +67 -0
- package/dist/components/tabs/Tabs.test.d.ts +1 -0
- package/dist/components/tabs/Tabs.test.js +61 -0
- package/dist/components/text-link/TextLink.d.ts +9 -0
- package/dist/components/text-link/TextLink.js +15 -0
- package/dist/components/text-link/TextLink.test.d.ts +1 -0
- package/dist/components/text-link/TextLink.test.js +14 -0
- package/dist/components/textarea/Textarea.d.ts +48 -0
- package/dist/components/textarea/Textarea.js +46 -0
- package/dist/components/timed-button/TimedButton.d.ts +56 -0
- package/dist/components/timed-button/TimedButton.js +106 -0
- package/dist/components/timed-button/TimedButton.test.d.ts +1 -0
- package/dist/components/timed-button/TimedButton.test.js +35 -0
- package/dist/components/toggle/Toggle.d.ts +62 -0
- package/dist/components/toggle/Toggle.js +67 -0
- package/dist/components/toggle/Toggle.test.d.ts +1 -0
- package/dist/components/toggle/Toggle.test.js +93 -0
- package/dist/components/typography/Heading.d.ts +17 -0
- package/dist/components/typography/Heading.js +11 -0
- package/dist/components/typography/Label.d.ts +15 -0
- package/dist/components/typography/Label.js +7 -0
- package/dist/components/typography/Paragraph.d.ts +15 -0
- package/dist/components/typography/Paragraph.js +7 -0
- package/dist/components/typography/Placeholder.d.ts +13 -0
- package/dist/components/typography/Placeholder.js +7 -0
- package/dist/components/typography/ValidationMessage.d.ts +15 -0
- package/dist/components/typography/ValidationMessage.js +9 -0
- package/dist/components/typography/styles.d.ts +74 -0
- package/dist/components/typography/styles.js +52 -0
- package/dist/favicon.svg +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +7550 -0
- package/dist/index.js +2 -0
- package/dist/logo-inline-gradient.svg +43 -0
- package/dist/setupTests.d.ts +1 -0
- package/dist/setupTests.js +7 -0
- package/package.json +78 -33
- package/.github/CODEOWNERS +0 -7
- package/.github/semantic.yml +0 -24
- package/.github/workflows/publish-npm.yml +0 -29
- package/.github/workflows/storybook.yml +0 -44
- package/.releaserc.json +0 -9
- package/.storybook/main.ts +0 -19
- package/.storybook/preview.ts +0 -21
- package/.storybook/vitest.setup.ts +0 -7
- package/src/index.ts +0 -4
- package/stories/Button.stories.ts +0 -54
- package/stories/Button.tsx +0 -37
- package/stories/Button2.stories.ts +0 -54
- package/stories/Button2.tsx +0 -41
- package/stories/Configure.mdx +0 -364
- package/stories/Header.stories.ts +0 -34
- package/stories/Header.tsx +0 -56
- package/stories/Page.stories.ts +0 -33
- package/stories/Page.tsx +0 -73
- package/stories/assets/accessibility.png +0 -0
- package/stories/assets/accessibility.svg +0 -1
- package/stories/assets/addon-library.png +0 -0
- package/stories/assets/assets.png +0 -0
- package/stories/assets/avif-test-image.avif +0 -0
- package/stories/assets/context.png +0 -0
- package/stories/assets/discord.svg +0 -1
- package/stories/assets/docs.png +0 -0
- package/stories/assets/figma-plugin.png +0 -0
- package/stories/assets/github.svg +0 -1
- package/stories/assets/share.png +0 -0
- package/stories/assets/styling.png +0 -0
- package/stories/assets/testing.png +0 -0
- package/stories/assets/theming.png +0 -0
- package/stories/assets/tutorials.svg +0 -1
- package/stories/assets/youtube.svg +0 -1
- package/stories/button.css +0 -30
- package/stories/button2.css +0 -30
- package/stories/header.css +0 -32
- package/stories/page.css +0 -68
- package/tsconfig.json +0 -13
- package/vitest.config.ts +0 -35
- 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 {};
|