untitledui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.js +339 -0
- package/dist/commands/init.js +436 -0
- package/dist/helper/download-tar-api.js +129 -0
- package/dist/helper/download-tar.js +81 -0
- package/dist/helper/find-css-file.js +19 -0
- package/dist/helper/formatText.js +37 -0
- package/dist/helper/get-components-api.js +47 -0
- package/dist/helper/get-components-list.js +62 -0
- package/dist/helper/get-components.js +19 -0
- package/dist/helper/get-config.js +163 -0
- package/dist/helper/get-package-info.js +99 -0
- package/dist/helper/get-pkg-manager.js +16 -0
- package/dist/helper/get-project.js +176 -0
- package/dist/helper/install-template.js +29 -0
- package/dist/helper/match-color-css.js +82 -0
- package/dist/helper/update-color-css.js +134 -0
- package/dist/index.js +25 -0
- package/dist/package.json +50 -0
- package/dist/res/components.json +520 -0
- package/dist/res/config.json +3 -0
- package/package.json +61 -0
- package/templates/default/.prettierrc +10 -0
- package/templates/default/README.md +36 -0
- package/templates/default/eslint.config.mjs +58 -0
- package/templates/default/next.config.ts +6 -0
- package/templates/default/package.json +57 -0
- package/templates/default/postcss.config.js +5 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/marketing/smiling-girl.png +0 -0
- package/templates/default/public/marketing/spirals.webp +0 -0
- package/templates/default/src/app/home-screen.tsx +109 -0
- package/templates/default/src/app/layout.tsx +42 -0
- package/templates/default/src/app/not-found.tsx +40 -0
- package/templates/default/src/app/page.tsx +3 -0
- package/templates/default/src/components/foundations/dot-icon.tsx +27 -0
- package/templates/default/src/components/foundations/featured-icon/featured-icons.tsx +153 -0
- package/templates/default/src/components/foundations/logo/UntitledLogo.tsx +63 -0
- package/templates/default/src/components/foundations/logo/UntitledLogoMinimal.tsx +164 -0
- package/templates/default/src/components/foundations/payment-icons/amex-icon.tsx +19 -0
- package/templates/default/src/components/foundations/payment-icons/apple-pay-icon.tsx +27 -0
- package/templates/default/src/components/foundations/payment-icons/discover-icon.tsx +34 -0
- package/templates/default/src/components/foundations/payment-icons/index.tsx +10 -0
- package/templates/default/src/components/foundations/payment-icons/mastercard-icon.tsx +39 -0
- package/templates/default/src/components/foundations/payment-icons/paypal-icon.tsx +45 -0
- package/templates/default/src/components/foundations/payment-icons/stripe-icon.tsx +27 -0
- package/templates/default/src/components/foundations/payment-icons/union-pay-icon.tsx +37 -0
- package/templates/default/src/components/foundations/payment-icons/visa-icon.tsx +27 -0
- package/templates/default/src/components/marketing/header-navigation/base-components/nav-menu-item.tsx +41 -0
- package/templates/default/src/components/marketing/header-navigation/components/header.tsx +245 -0
- package/templates/default/src/components/marketing/header-navigation/dropdown-header-navigation.tsx +53 -0
- package/templates/default/src/components/shared/avatar/avatar-label-group.tsx +32 -0
- package/templates/default/src/components/shared/avatar/avatar-profile-photo.tsx +84 -0
- package/templates/default/src/components/shared/avatar/avatar.tsx +131 -0
- package/templates/default/src/components/shared/avatar/base-components/avatar-add-button.tsx +33 -0
- package/templates/default/src/components/shared/avatar/base-components/avatar-company-icon.tsx +26 -0
- package/templates/default/src/components/shared/avatar/base-components/avatar-online-indicator.tsx +31 -0
- package/templates/default/src/components/shared/avatar/base-components/index.ts +4 -0
- package/templates/default/src/components/shared/avatar/base-components/verified-tick.tsx +34 -0
- package/templates/default/src/components/shared/avatar/utils.ts +12 -0
- package/templates/default/src/components/shared/badges/badge-groups.tsx +176 -0
- package/templates/default/src/components/shared/badges/badge-types.ts +264 -0
- package/templates/default/src/components/shared/badges/badges.tsx +479 -0
- package/templates/default/src/components/shared/button-group/button-group.tsx +97 -0
- package/templates/default/src/components/shared/buttons/app-store-buttons-outline.tsx +454 -0
- package/templates/default/src/components/shared/buttons/app-store-buttons.tsx +806 -0
- package/templates/default/src/components/shared/buttons/button-utility.tsx +87 -0
- package/templates/default/src/components/shared/buttons/button.tsx +284 -0
- package/templates/default/src/components/shared/buttons/close-button.tsx +39 -0
- package/templates/default/src/components/shared/buttons/social-button.tsx +135 -0
- package/templates/default/src/components/shared/buttons/social-logos.tsx +115 -0
- package/templates/default/src/components/shared/checkbox/checkbox.tsx +120 -0
- package/templates/default/src/components/shared/dropdown/dropdown.tsx +138 -0
- package/templates/default/src/components/shared/input-dropdown/combobox.tsx +161 -0
- package/templates/default/src/components/shared/input-dropdown/dropdown-item.tsx +98 -0
- package/templates/default/src/components/shared/input-dropdown/input-dropdown.tsx +172 -0
- package/templates/default/src/components/shared/input-dropdown/multi-select.tsx +373 -0
- package/templates/default/src/components/shared/input-dropdown/popover.tsx +36 -0
- package/templates/default/src/components/shared/input-dropdown/select.tsx +63 -0
- package/templates/default/src/components/shared/inputs/file-upload-trigger.tsx +74 -0
- package/templates/default/src/components/shared/inputs/form/form.tsx +10 -0
- package/templates/default/src/components/shared/inputs/hint-text.tsx +34 -0
- package/templates/default/src/components/shared/inputs/input/index.tsx +189 -0
- package/templates/default/src/components/shared/inputs/input/input-payment.tsx +134 -0
- package/templates/default/src/components/shared/inputs/input/input-with-button.tsx +69 -0
- package/templates/default/src/components/shared/inputs/input/input-with-dropdown.tsx +178 -0
- package/templates/default/src/components/shared/inputs/input/input-with-prefix.tsx +74 -0
- package/templates/default/src/components/shared/inputs/label.tsx +46 -0
- package/templates/default/src/components/shared/inputs/textarea/textarea.tsx +82 -0
- package/templates/default/src/components/shared/progress-indicators/progress-circles.tsx +176 -0
- package/templates/default/src/components/shared/progress-indicators/progress-indicators.tsx +86 -0
- package/templates/default/src/components/shared/progress-indicators/simple-circle.tsx +29 -0
- package/templates/default/src/components/shared/radio-buttons/radio-buttons.tsx +125 -0
- package/templates/default/src/components/shared/radio-groups/radio-group-avatar.tsx +62 -0
- package/templates/default/src/components/shared/radio-groups/radio-group-checkbox.tsx +72 -0
- package/templates/default/src/components/shared/radio-groups/radio-group-icon-card.tsx +95 -0
- package/templates/default/src/components/shared/radio-groups/radio-group-icon-simple.tsx +70 -0
- package/templates/default/src/components/shared/radio-groups/radio-group-payment-icon.tsx +71 -0
- package/templates/default/src/components/shared/radio-groups/radio-group-radio-button.tsx +76 -0
- package/templates/default/src/components/shared/radio-groups/radio-groups.tsx +8 -0
- package/templates/default/src/components/shared/slider/slider.tsx +76 -0
- package/templates/default/src/components/shared/tags/base-components/tag-checkbox.tsx +47 -0
- package/templates/default/src/components/shared/tags/base-components/tag-close-x.tsx +34 -0
- package/templates/default/src/components/shared/tags/tags.tsx +162 -0
- package/templates/default/src/components/shared/toggle/toggle.tsx +140 -0
- package/templates/default/src/components/shared/tooltips/tooltips.tsx +140 -0
- package/templates/default/src/components/utils/index.ts +48 -0
- package/templates/default/src/components/utils/isDeepEqual.ts +31 -0
- package/templates/default/src/components/utils/isReactComponent.ts +22 -0
- package/templates/default/src/components/utils/mergeRefs.ts +19 -0
- package/templates/default/src/components/utils/useBreakpoint.ts +36 -0
- package/templates/default/src/components/utils/uuid.ts +9 -0
- package/templates/default/src/fonts/GeistMonoVF.woff +0 -0
- package/templates/default/src/fonts/GeistVF.woff +0 -0
- package/templates/default/src/hooks/use-resize-observer.tsx +55 -0
- package/templates/default/src/providers/theme.tsx +11 -0
- package/templates/default/src/styles/colors.css +805 -0
- package/templates/default/src/styles/globals.css +86 -0
- package/templates/default/src/styles/text-styles.css +177 -0
- package/templates/default/src/styles/theme.css +1310 -0
- package/templates/default/src/styles/typography.css +428 -0
- package/templates/default/tsconfig.json +27 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type PropsWithChildren, type RefAttributes, createContext, useContext } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Tag as AriaTag,
|
|
6
|
+
TagGroup as AriaTagGroup,
|
|
7
|
+
type TagGroupProps as AriaTagGroupProps,
|
|
8
|
+
TagList as AriaTagList,
|
|
9
|
+
type TagProps as AriaTagProps,
|
|
10
|
+
} from "react-aria-components";
|
|
11
|
+
import Dot from "@/components/foundations/dot-icon";
|
|
12
|
+
import { cx } from "@/components/utils";
|
|
13
|
+
import Avatar from "../avatar/avatar";
|
|
14
|
+
import TagCheckbox from "./base-components/tag-checkbox";
|
|
15
|
+
import { TagCloseX } from "./base-components/tag-close-x";
|
|
16
|
+
|
|
17
|
+
export interface TagItem {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
count?: number;
|
|
21
|
+
avatarSrc?: string;
|
|
22
|
+
avatarContrastBorder?: boolean;
|
|
23
|
+
dot?: boolean;
|
|
24
|
+
dotClassName?: string;
|
|
25
|
+
isDisabled?: boolean;
|
|
26
|
+
onClose?: (id: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TagGroupContext = createContext<{
|
|
30
|
+
selectionMode: "none" | "single" | "multiple";
|
|
31
|
+
size: "sm" | "md" | "lg";
|
|
32
|
+
}>({
|
|
33
|
+
selectionMode: "none",
|
|
34
|
+
size: "sm",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
interface TagGroupProps extends AriaTagGroupProps, RefAttributes<HTMLDivElement> {
|
|
38
|
+
label: string;
|
|
39
|
+
size?: "sm" | "md" | "lg";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const TagGroup = ({ label, selectionMode = "none", size = "sm", children, ...otherProps }: TagGroupProps) => {
|
|
43
|
+
return (
|
|
44
|
+
<TagGroupContext.Provider value={{ selectionMode, size }}>
|
|
45
|
+
<AriaTagGroup aria-label={label} selectionMode={selectionMode} disallowEmptySelection={selectionMode === "single"} {...otherProps}>
|
|
46
|
+
{children}
|
|
47
|
+
</AriaTagGroup>
|
|
48
|
+
</TagGroupContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const TagList = AriaTagList;
|
|
53
|
+
|
|
54
|
+
const styles = {
|
|
55
|
+
sm: {
|
|
56
|
+
root: {
|
|
57
|
+
base: "px-2 py-[3px] tt-xs-md",
|
|
58
|
+
withCheckbox: "pl-[5px]",
|
|
59
|
+
withAvatar: "pl-1",
|
|
60
|
+
withDot: "pl-1.5",
|
|
61
|
+
withCount: "pr-1",
|
|
62
|
+
withClose: "pr-1",
|
|
63
|
+
},
|
|
64
|
+
content: "gap-1",
|
|
65
|
+
count: "px-1 tt-xs-md",
|
|
66
|
+
},
|
|
67
|
+
md: {
|
|
68
|
+
root: {
|
|
69
|
+
base: "px-[9px] py-[2px] tt-sm-md",
|
|
70
|
+
withCheckbox: "pl-1",
|
|
71
|
+
withAvatar: "pl-[5px]",
|
|
72
|
+
withDot: "pl-[7px]",
|
|
73
|
+
withCount: "pr-[3px]",
|
|
74
|
+
withClose: "pr-1",
|
|
75
|
+
},
|
|
76
|
+
content: "gap-[5px]",
|
|
77
|
+
count: "px-[5px] tt-xs-md",
|
|
78
|
+
},
|
|
79
|
+
lg: {
|
|
80
|
+
root: {
|
|
81
|
+
base: "px-2.5 py-[4px] tt-sm-md",
|
|
82
|
+
withCheckbox: "pl-[5px]",
|
|
83
|
+
withAvatar: "pl-[7px]",
|
|
84
|
+
withDot: "pl-[9px]",
|
|
85
|
+
withCount: "pr-1",
|
|
86
|
+
withClose: "pr-1",
|
|
87
|
+
},
|
|
88
|
+
content: "gap-1.5",
|
|
89
|
+
count: "px-1.5 tt-sm-md",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
interface TagProps extends AriaTagProps, RefAttributes<object>, Omit<TagItem, "label" | "id"> {}
|
|
94
|
+
|
|
95
|
+
export const Tag = ({
|
|
96
|
+
id,
|
|
97
|
+
avatarSrc,
|
|
98
|
+
avatarContrastBorder,
|
|
99
|
+
dot,
|
|
100
|
+
dotClassName,
|
|
101
|
+
isDisabled,
|
|
102
|
+
count,
|
|
103
|
+
className,
|
|
104
|
+
children,
|
|
105
|
+
onClose,
|
|
106
|
+
}: PropsWithChildren<TagProps>) => {
|
|
107
|
+
const context = useContext(TagGroupContext);
|
|
108
|
+
|
|
109
|
+
const leadingContent = avatarSrc ? (
|
|
110
|
+
<Avatar size="xxs" src={avatarSrc} alt="Avatar" contrastBorder={avatarContrastBorder} />
|
|
111
|
+
) : dot ? (
|
|
112
|
+
<Dot className={cx("text-fg-success-secondary", dotClassName)} size="sm" />
|
|
113
|
+
) : null;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<AriaTag
|
|
117
|
+
id={id}
|
|
118
|
+
isDisabled={isDisabled}
|
|
119
|
+
className={(state) =>
|
|
120
|
+
cx(
|
|
121
|
+
"flex cursor-default items-center gap-[3px] rounded-md bg-primary text-secondary ring-1 ring-border-primary ring-inset focus:outline-hidden focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring",
|
|
122
|
+
styles[context.size].root.base,
|
|
123
|
+
|
|
124
|
+
// With avatar
|
|
125
|
+
avatarSrc && styles[context.size].root.withAvatar,
|
|
126
|
+
// With X button
|
|
127
|
+
(onClose || state.allowsRemoving) && styles[context.size].root.withClose,
|
|
128
|
+
// With dot
|
|
129
|
+
dot && styles[context.size].root.withDot,
|
|
130
|
+
// With count
|
|
131
|
+
typeof count === "number" && styles[context.size].root.withCount,
|
|
132
|
+
// With checkbox
|
|
133
|
+
context.selectionMode !== "none" && styles[context.size].root.withCheckbox,
|
|
134
|
+
// Disabled
|
|
135
|
+
isDisabled && "cursor-not-allowed",
|
|
136
|
+
|
|
137
|
+
typeof className === "function" ? className(state) : className,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
>
|
|
141
|
+
{({ isSelected, isDisabled, allowsRemoving }) => (
|
|
142
|
+
<>
|
|
143
|
+
<div className={cx("flex items-center gap-1", styles[context.size].content)}>
|
|
144
|
+
{context.selectionMode !== "none" && <TagCheckbox size={context.size} isSelected={isSelected} isDisabled={isDisabled} />}
|
|
145
|
+
|
|
146
|
+
{leadingContent}
|
|
147
|
+
|
|
148
|
+
{children}
|
|
149
|
+
|
|
150
|
+
{typeof count === "number" && (
|
|
151
|
+
<span className={cx("flex items-center justify-center rounded-[3px] bg-tertiary text-center", styles[context.size].count)}>
|
|
152
|
+
{count}
|
|
153
|
+
</span>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
{(onClose || allowsRemoving) && <TagCloseX size={context.size} onPress={() => id && onClose?.(id.toString())} />}
|
|
158
|
+
</>
|
|
159
|
+
)}
|
|
160
|
+
</AriaTag>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { SwitchProps } from "react-aria-components";
|
|
5
|
+
import { Switch } from "react-aria-components";
|
|
6
|
+
import { cx } from "@/components/utils";
|
|
7
|
+
|
|
8
|
+
interface ToggleBaseProps {
|
|
9
|
+
size?: "sm" | "md";
|
|
10
|
+
slim?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
isHovered?: boolean;
|
|
13
|
+
isFocused?: boolean;
|
|
14
|
+
isSelected?: boolean;
|
|
15
|
+
isDisabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ToggleBase = ({ className, isHovered, isDisabled, isFocused, isSelected, slim, size = "sm" }: ToggleBaseProps) => {
|
|
19
|
+
const styles = {
|
|
20
|
+
default: {
|
|
21
|
+
sm: {
|
|
22
|
+
root: "h-5 w-9 p-0.5",
|
|
23
|
+
switch: cx("size-4", isSelected && "translate-x-4"),
|
|
24
|
+
},
|
|
25
|
+
md: {
|
|
26
|
+
root: "h-6 w-11 p-0.5",
|
|
27
|
+
switch: cx("size-5", isSelected && "translate-x-5"),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
slim: {
|
|
31
|
+
sm: {
|
|
32
|
+
root: "h-4 w-8",
|
|
33
|
+
switch: cx("size-4", isSelected && "translate-x-4"),
|
|
34
|
+
},
|
|
35
|
+
md: {
|
|
36
|
+
root: "h-5 w-10",
|
|
37
|
+
switch: cx("size-5", isSelected && "translate-x-5"),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const classes = slim ? styles.slim[size] : styles.default[size];
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
className={cx(
|
|
47
|
+
"cursor-pointer rounded-full bg-tertiary outline-focus-ring transition duration-150 ease-linear",
|
|
48
|
+
isSelected && "bg-brand-solid",
|
|
49
|
+
isSelected && isHovered && "bg-brand-solid_hover",
|
|
50
|
+
isDisabled && "cursor-not-allowed bg-disabled",
|
|
51
|
+
isFocused && "outline-2 outline-offset-2",
|
|
52
|
+
|
|
53
|
+
slim && "ring-1 ring-border-secondary ring-inset",
|
|
54
|
+
slim && isSelected && "ring-transparent",
|
|
55
|
+
classes.root,
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
style={{
|
|
61
|
+
transition: "transform 0.15s ease-in-out, translate 0.15s ease-in-out, border-color 0.1s linear, background-color 0.1s linear",
|
|
62
|
+
}}
|
|
63
|
+
className={cx(
|
|
64
|
+
"rounded-full bg-fg-white shadow-sm",
|
|
65
|
+
isDisabled && "bg-toggle-button-fg_disabled",
|
|
66
|
+
|
|
67
|
+
slim && "shadow-xs",
|
|
68
|
+
slim && "border border-toggle-border",
|
|
69
|
+
slim && isSelected && "border-toggle-slim-border_pressed",
|
|
70
|
+
slim && isSelected && isHovered && "border-toggle-slim-border_pressed-hover",
|
|
71
|
+
|
|
72
|
+
classes.switch,
|
|
73
|
+
)}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
interface ToggleProps extends SwitchProps {
|
|
80
|
+
size?: "sm" | "md";
|
|
81
|
+
label?: string;
|
|
82
|
+
hint?: ReactNode;
|
|
83
|
+
slim?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const Toggle = ({ label, hint, className, size = "sm", slim, ...ariaSwitchProps }: ToggleProps) => {
|
|
87
|
+
const sizes = {
|
|
88
|
+
sm: {
|
|
89
|
+
root: "gap-2",
|
|
90
|
+
textWrapper: "",
|
|
91
|
+
label: "tt-sm-md",
|
|
92
|
+
hint: "tt-sm",
|
|
93
|
+
},
|
|
94
|
+
md: {
|
|
95
|
+
root: "gap-3",
|
|
96
|
+
textWrapper: "gap-0.5",
|
|
97
|
+
label: "tt-md-md",
|
|
98
|
+
hint: "tt-md",
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<Switch
|
|
104
|
+
{...ariaSwitchProps}
|
|
105
|
+
className={(renderProps) =>
|
|
106
|
+
cx(
|
|
107
|
+
"flex w-max items-start",
|
|
108
|
+
renderProps.isDisabled && "cursor-not-allowed",
|
|
109
|
+
sizes[size].root,
|
|
110
|
+
typeof className === "function" ? className(renderProps) : className,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
{({ isSelected, isDisabled, isFocused, isHovered }) => (
|
|
115
|
+
<>
|
|
116
|
+
<ToggleBase
|
|
117
|
+
slim={slim}
|
|
118
|
+
size={size}
|
|
119
|
+
isHovered={isHovered}
|
|
120
|
+
isDisabled={isDisabled}
|
|
121
|
+
isFocused={isFocused}
|
|
122
|
+
isSelected={isSelected}
|
|
123
|
+
className={slim ? "mt-0.5" : ""}
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
{(label || hint) && (
|
|
127
|
+
<div className={cx("flex flex-col", sizes[size].textWrapper)}>
|
|
128
|
+
{label && <p className={cx("text-secondary select-none", sizes[size].label)}>{label}</p>}
|
|
129
|
+
{hint && (
|
|
130
|
+
<span className={cx("text-tertiary", sizes[size].hint)} onClick={(event) => event.stopPropagation()}>
|
|
131
|
+
{hint}
|
|
132
|
+
</span>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</>
|
|
137
|
+
)}
|
|
138
|
+
</Switch>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { DetailedReactHTMLElement, ReactNode, RefAttributes } from "react";
|
|
4
|
+
import { cloneElement, useRef } from "react";
|
|
5
|
+
import type { Placement } from "@react-types/overlays";
|
|
6
|
+
import type { FocusableElement } from "@react-types/shared";
|
|
7
|
+
import { mergeProps, useFocusable } from "react-aria";
|
|
8
|
+
import { Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger, Button, OverlayArrow } from "react-aria-components";
|
|
9
|
+
import type { TooltipProps as AriaTooltipProps, ButtonProps, TooltipTriggerComponentProps } from "react-aria-components";
|
|
10
|
+
import { cx } from "@/components/utils";
|
|
11
|
+
|
|
12
|
+
const padding: Partial<Record<Placement, string>> = {
|
|
13
|
+
"top left": "px-2.5",
|
|
14
|
+
"top right": "px-2.5",
|
|
15
|
+
"bottom left": "px-2.5",
|
|
16
|
+
"bottom right": "px-2.5",
|
|
17
|
+
left: "-ml-px",
|
|
18
|
+
"left top": "-ml-px",
|
|
19
|
+
"left bottom": "-ml-px",
|
|
20
|
+
start: "-ml-px",
|
|
21
|
+
"start top": "-ml-px",
|
|
22
|
+
"start bottom": "-ml-px",
|
|
23
|
+
right: "-mr-px",
|
|
24
|
+
"right top": "-mr-px",
|
|
25
|
+
"right bottom": "-mr-px",
|
|
26
|
+
end: "-mr-px",
|
|
27
|
+
"end top": "-mr-px",
|
|
28
|
+
"end bottom": "-mr-px",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface TooltipProps extends TooltipTriggerComponentProps, Omit<AriaTooltipProps, "children"> {
|
|
32
|
+
title: ReactNode;
|
|
33
|
+
description?: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* Whether to show the arrow on the tooltip.
|
|
36
|
+
*
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
arrow?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Delay in milliseconds before the tooltip is shown.
|
|
42
|
+
*
|
|
43
|
+
* @default 300
|
|
44
|
+
*/
|
|
45
|
+
delay?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Tooltip = ({
|
|
49
|
+
title,
|
|
50
|
+
description,
|
|
51
|
+
children,
|
|
52
|
+
arrow = false,
|
|
53
|
+
delay = 300,
|
|
54
|
+
closeDelay,
|
|
55
|
+
trigger,
|
|
56
|
+
isDisabled,
|
|
57
|
+
isOpen,
|
|
58
|
+
defaultOpen,
|
|
59
|
+
onOpenChange,
|
|
60
|
+
...tooltipProps
|
|
61
|
+
}: TooltipProps) => {
|
|
62
|
+
return (
|
|
63
|
+
<AriaTooltipTrigger {...{ trigger, delay, closeDelay, isDisabled, isOpen, defaultOpen, onOpenChange }}>
|
|
64
|
+
{children}
|
|
65
|
+
|
|
66
|
+
<AriaTooltip
|
|
67
|
+
offset={6}
|
|
68
|
+
{...tooltipProps}
|
|
69
|
+
className={({ isEntering, isExiting }) =>
|
|
70
|
+
cx(
|
|
71
|
+
isEntering &&
|
|
72
|
+
"ease-out animate-in fade-in zoom-in-95 placement-top:origin-bottom placement-top:slide-in-from-bottom-0.5 placement-bottom:origin-top placement-bottom:slide-in-from-top-0.5",
|
|
73
|
+
isExiting &&
|
|
74
|
+
"ease-in animate-out fade-out zoom-out-95 placement-top:origin-bottom placement-top:slide-out-to-bottom-0.5 placement-bottom:origin-top placement-bottom:slide-out-to-top-0.5",
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
{arrow && (
|
|
79
|
+
<OverlayArrow className={cx(tooltipProps.placement && padding[tooltipProps.placement as keyof typeof padding], "group")}>
|
|
80
|
+
<svg
|
|
81
|
+
width={8}
|
|
82
|
+
height={8}
|
|
83
|
+
viewBox="0 0 8 8"
|
|
84
|
+
className="fill-bg-primary-solid group-data-[placement=bottom]:rotate-180 group-data-[placement=left]:-rotate-90 group-data-[placement=right]:rotate-90 group-data-[placement=top]:rotate-0"
|
|
85
|
+
>
|
|
86
|
+
<path d="M0 0 L4 4 L8 0" />
|
|
87
|
+
</svg>
|
|
88
|
+
</OverlayArrow>
|
|
89
|
+
)}
|
|
90
|
+
<div className={cx("z-50 flex max-w-xs flex-col items-start gap-1 rounded-lg bg-primary-solid px-3 shadow-lg", description ? "py-3" : "py-2")}>
|
|
91
|
+
<span className="tt-xs-semi text-white">{title}</span>
|
|
92
|
+
|
|
93
|
+
{description && <span className="tt-xs-md text-tooltip-supporting-text">{description}</span>}
|
|
94
|
+
</div>
|
|
95
|
+
</AriaTooltip>
|
|
96
|
+
</AriaTooltipTrigger>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
type TooltipTriggerProps =
|
|
101
|
+
| (ButtonProps &
|
|
102
|
+
RefAttributes<HTMLButtonElement> & {
|
|
103
|
+
/**
|
|
104
|
+
* If true, the tooltip trigger props will be passed down to the child element
|
|
105
|
+
* instead of wrapping the child element in a button.
|
|
106
|
+
*/
|
|
107
|
+
asChild?: never;
|
|
108
|
+
})
|
|
109
|
+
| {
|
|
110
|
+
/**
|
|
111
|
+
* If true, the tooltip trigger props will be passed down to the child element
|
|
112
|
+
* instead of wrapping the child element in a button.
|
|
113
|
+
*/
|
|
114
|
+
asChild: true;
|
|
115
|
+
isDisabled?: boolean;
|
|
116
|
+
children: Omit<DetailedReactHTMLElement<any, any>, "ref">;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const TooltipTrigger = (props: TooltipTriggerProps) => {
|
|
120
|
+
if (props.asChild) {
|
|
121
|
+
const triggerRef = useRef<FocusableElement>(null);
|
|
122
|
+
|
|
123
|
+
const { focusableProps } = useFocusable(
|
|
124
|
+
{
|
|
125
|
+
isDisabled: props.isDisabled,
|
|
126
|
+
},
|
|
127
|
+
triggerRef,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return cloneElement(props.children, mergeProps(focusableProps, props.children.props, { ref: triggerRef }));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { asChild: _, className, ...buttonProps } = props;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Button {...buttonProps} className={(values) => cx("h-max w-max outline-hidden", typeof className === "function" ? className(values) : className)}>
|
|
137
|
+
{props.children}
|
|
138
|
+
</Button>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
2
|
+
|
|
3
|
+
const twMerge = extendTailwindMerge<"spacing">({
|
|
4
|
+
extend: {
|
|
5
|
+
classGroups: {
|
|
6
|
+
"font-size": [
|
|
7
|
+
"tt-xs",
|
|
8
|
+
"tt-sm",
|
|
9
|
+
"tt-md",
|
|
10
|
+
"tt-lg",
|
|
11
|
+
"tt-xl",
|
|
12
|
+
"td-xs",
|
|
13
|
+
"td-sm",
|
|
14
|
+
"td-md",
|
|
15
|
+
"td-lg",
|
|
16
|
+
"td-xl",
|
|
17
|
+
|
|
18
|
+
"tt-xs-md",
|
|
19
|
+
"tt-sm-md",
|
|
20
|
+
"tt-md-md",
|
|
21
|
+
"tt-lg-md",
|
|
22
|
+
"tt-xl-md",
|
|
23
|
+
"td-xs-md",
|
|
24
|
+
"td-sm-md",
|
|
25
|
+
"td-md-md",
|
|
26
|
+
"td-lg-md",
|
|
27
|
+
"td-xl-md",
|
|
28
|
+
|
|
29
|
+
"tt-xs-semi",
|
|
30
|
+
"tt-sm-semi",
|
|
31
|
+
"tt-md-semi",
|
|
32
|
+
"tt-lg-semi",
|
|
33
|
+
"tt-xl-semi",
|
|
34
|
+
"td-xs-semi",
|
|
35
|
+
"td-sm-semi",
|
|
36
|
+
"td-md-semi",
|
|
37
|
+
"td-lg-semi",
|
|
38
|
+
"td-xl-semi",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const cx = twMerge;
|
|
45
|
+
|
|
46
|
+
export function sortCx<T extends Record<string, string | number | Record<string, string | number | Record<string, string | number>>>>(classes: T): T {
|
|
47
|
+
return classes;
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares two values to determine if they are deeply equal.
|
|
3
|
+
*
|
|
4
|
+
* @param obj1 - The first value to compare.
|
|
5
|
+
* @param obj2 - The second value to compare.
|
|
6
|
+
*
|
|
7
|
+
* @returns True if the values are deeply equal, otherwise false.
|
|
8
|
+
*/
|
|
9
|
+
export const isDeepEqual = (obj1: any, obj2: any): boolean => {
|
|
10
|
+
// Check if both are the same reference
|
|
11
|
+
if (obj1 === obj2) return true;
|
|
12
|
+
|
|
13
|
+
// Check if both are objects and not null
|
|
14
|
+
if (typeof obj1 !== "object" || obj1 === null || typeof obj2 !== "object" || obj2 === null) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check if they have the same number of keys
|
|
19
|
+
const keys1 = Object.keys(obj1);
|
|
20
|
+
const keys2 = Object.keys(obj2);
|
|
21
|
+
if (keys1.length !== keys2.length) return false;
|
|
22
|
+
|
|
23
|
+
// Recursively check each key
|
|
24
|
+
for (const key of keys1) {
|
|
25
|
+
if (!keys2.includes(key) || !isDeepEqual(obj1[key], obj2[key])) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
|
|
3
|
+
type ReactComponent = React.FC<any> | React.ComponentClass<any, any>;
|
|
4
|
+
|
|
5
|
+
export const isFunctionComponent = (component: any): component is React.FC<any> => {
|
|
6
|
+
return typeof component === "function";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const isClassComponent = (component: any): component is React.ComponentClass<any, any> => {
|
|
10
|
+
return typeof component === "function" && component.prototype && (!!component.prototype.isReactComponent || !!component.prototype.render);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const isForwardRefComponent = (component: any): component is React.ForwardRefExoticComponent<any> => {
|
|
14
|
+
return typeof component === "object" && component !== null && component.$$typeof.toString() === "Symbol(react.forward_ref)";
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Checks if a given value is a valid React component.
|
|
19
|
+
*/
|
|
20
|
+
export const isReactComponent = (component: any): component is ReactComponent => {
|
|
21
|
+
return isFunctionComponent(component) || isForwardRefComponent(component) || isClassComponent(component);
|
|
22
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges multiple React refs into a single ref callback.
|
|
3
|
+
*
|
|
4
|
+
* @param refs - An array of refs which can be mutable ref objects, legacy refs, or undefined/null.
|
|
5
|
+
* @returns A ref callback function that assigns the given value to all provided refs.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam T - The type of the ref value.
|
|
8
|
+
*/
|
|
9
|
+
export function mergeRefs<T = any>(refs: Array<React.MutableRefObject<T> | React.LegacyRef<T> | undefined | null>): React.RefCallback<T> {
|
|
10
|
+
return (value) => {
|
|
11
|
+
refs.forEach((ref) => {
|
|
12
|
+
if (typeof ref === "function") {
|
|
13
|
+
ref(value);
|
|
14
|
+
} else if (ref != null) {
|
|
15
|
+
(ref as React.MutableRefObject<T | null>).current = value;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const screens = {
|
|
4
|
+
sm: "640px",
|
|
5
|
+
md: "768px",
|
|
6
|
+
lg: "1024px",
|
|
7
|
+
xl: "1280px",
|
|
8
|
+
"2xl": "1536px",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Checks whether a particular Tailwind viewport size applies.
|
|
13
|
+
*
|
|
14
|
+
* @param size The size to check, which must either be included in Tailwind's
|
|
15
|
+
* list of default screen sizes, or added to the Tailwind config file.
|
|
16
|
+
*
|
|
17
|
+
* @returns A boolean indicating whether the viewport size applies.
|
|
18
|
+
*/
|
|
19
|
+
const useBreakpoint = (size: "sm" | "md" | "lg" | "xl" | "2xl") => {
|
|
20
|
+
const [matches, setMatches] = useState(typeof window !== "undefined" ? window.matchMedia(`(min-width: ${screens[size]})`).matches : true);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const breakpoint = window.matchMedia(`(min-width: ${screens[size]})`);
|
|
24
|
+
|
|
25
|
+
setMatches(breakpoint.matches);
|
|
26
|
+
|
|
27
|
+
const handleChange = (value: MediaQueryListEvent) => setMatches(value.matches);
|
|
28
|
+
|
|
29
|
+
breakpoint.addEventListener("change", handleChange);
|
|
30
|
+
return () => breakpoint.removeEventListener("change", handleChange);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return matches;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default useBreakpoint;
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect } from "react";
|
|
13
|
+
import type { RefObject } from "@react-types/shared";
|
|
14
|
+
|
|
15
|
+
function hasResizeObserver() {
|
|
16
|
+
return typeof window.ResizeObserver !== "undefined";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type useResizeObserverOptionsType<T> = {
|
|
20
|
+
ref: RefObject<T | undefined | null> | undefined;
|
|
21
|
+
box?: ResizeObserverBoxOptions;
|
|
22
|
+
onResize: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>) {
|
|
26
|
+
const { ref, box, onResize } = options;
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let element = ref?.current;
|
|
30
|
+
if (!element) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!hasResizeObserver()) {
|
|
35
|
+
window.addEventListener("resize", onResize, false);
|
|
36
|
+
return () => {
|
|
37
|
+
window.removeEventListener("resize", onResize, false);
|
|
38
|
+
};
|
|
39
|
+
} else {
|
|
40
|
+
const resizeObserverInstance = new window.ResizeObserver((entries) => {
|
|
41
|
+
if (!entries.length) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onResize();
|
|
46
|
+
});
|
|
47
|
+
resizeObserverInstance.observe(element, { box });
|
|
48
|
+
return () => {
|
|
49
|
+
if (element) {
|
|
50
|
+
resizeObserverInstance.unobserve(element);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}, [onResize, ref, box]);
|
|
55
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider } from "next-themes";
|
|
4
|
+
|
|
5
|
+
export function Theme({ children }: { children: React.ReactNode }) {
|
|
6
|
+
return (
|
|
7
|
+
<ThemeProvider attribute="data-theme" enableColorScheme enableSystem>
|
|
8
|
+
{children}
|
|
9
|
+
</ThemeProvider>
|
|
10
|
+
);
|
|
11
|
+
}
|