untitledui 0.1.5 → 0.1.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/config/postcss.config.mjs +6 -0
- package/{templates/default/src → config}/styles/globals.css +2 -33
- package/{templates/default/src → config}/styles/theme.css +31 -33
- package/{templates/default/src → config}/styles/typography.css +24 -24
- package/dist/index.mjs +54 -15
- package/package.json +4 -3
- package/templates/default/.prettierrc +0 -10
- package/templates/default/README.md +0 -36
- package/templates/default/eslint.config.mjs +0 -58
- package/templates/default/next.config.ts +0 -6
- package/templates/default/package.json +0 -56
- package/templates/default/postcss.config.js +0 -5
- 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 +0 -108
- package/templates/default/src/app/layout.tsx +0 -34
- package/templates/default/src/app/not-found.tsx +0 -40
- package/templates/default/src/app/page.tsx +0 -3
- package/templates/default/src/components/foundations/dot-icon.tsx +0 -27
- package/templates/default/src/components/foundations/featured-icon/featured-icons.tsx +0 -153
- package/templates/default/src/components/foundations/logo/UntitledLogo.tsx +0 -63
- package/templates/default/src/components/foundations/logo/UntitledLogoMinimal.tsx +0 -164
- package/templates/default/src/components/foundations/payment-icons/amex-icon.tsx +0 -19
- package/templates/default/src/components/foundations/payment-icons/apple-pay-icon.tsx +0 -27
- package/templates/default/src/components/foundations/payment-icons/discover-icon.tsx +0 -34
- package/templates/default/src/components/foundations/payment-icons/index.tsx +0 -10
- package/templates/default/src/components/foundations/payment-icons/mastercard-icon.tsx +0 -39
- package/templates/default/src/components/foundations/payment-icons/paypal-icon.tsx +0 -45
- package/templates/default/src/components/foundations/payment-icons/stripe-icon.tsx +0 -27
- package/templates/default/src/components/foundations/payment-icons/union-pay-icon.tsx +0 -37
- package/templates/default/src/components/foundations/payment-icons/visa-icon.tsx +0 -27
- package/templates/default/src/components/marketing/header-navigation/base-components/nav-menu-item.tsx +0 -41
- package/templates/default/src/components/marketing/header-navigation/components/header.tsx +0 -245
- package/templates/default/src/components/marketing/header-navigation/dropdown-header-navigation.tsx +0 -53
- package/templates/default/src/components/shared/avatar/avatar-label-group.tsx +0 -32
- package/templates/default/src/components/shared/avatar/avatar-profile-photo.tsx +0 -84
- package/templates/default/src/components/shared/avatar/avatar.tsx +0 -131
- package/templates/default/src/components/shared/avatar/base-components/avatar-add-button.tsx +0 -33
- package/templates/default/src/components/shared/avatar/base-components/avatar-company-icon.tsx +0 -26
- package/templates/default/src/components/shared/avatar/base-components/avatar-online-indicator.tsx +0 -31
- package/templates/default/src/components/shared/avatar/base-components/index.ts +0 -4
- package/templates/default/src/components/shared/avatar/base-components/verified-tick.tsx +0 -34
- package/templates/default/src/components/shared/avatar/utils.ts +0 -12
- package/templates/default/src/components/shared/badges/badge-groups.tsx +0 -176
- package/templates/default/src/components/shared/badges/badge-types.ts +0 -264
- package/templates/default/src/components/shared/badges/badges.tsx +0 -479
- package/templates/default/src/components/shared/button-group/button-group.tsx +0 -97
- package/templates/default/src/components/shared/buttons/app-store-buttons-outline.tsx +0 -454
- package/templates/default/src/components/shared/buttons/app-store-buttons.tsx +0 -806
- package/templates/default/src/components/shared/buttons/button-utility.tsx +0 -87
- package/templates/default/src/components/shared/buttons/button.tsx +0 -285
- package/templates/default/src/components/shared/buttons/close-button.tsx +0 -39
- package/templates/default/src/components/shared/buttons/social-button.tsx +0 -135
- package/templates/default/src/components/shared/buttons/social-logos.tsx +0 -115
- package/templates/default/src/components/shared/checkbox/checkbox.tsx +0 -120
- package/templates/default/src/components/shared/dropdown/dropdown.tsx +0 -147
- package/templates/default/src/components/shared/file-upload-trigger/file-upload-trigger.tsx +0 -74
- package/templates/default/src/components/shared/form/form.tsx +0 -10
- package/templates/default/src/components/shared/form/hook-form.tsx +0 -75
- package/templates/default/src/components/shared/input/hint-text.tsx +0 -34
- package/templates/default/src/components/shared/input/index.tsx +0 -189
- package/templates/default/src/components/shared/input/input-payment.tsx +0 -134
- package/templates/default/src/components/shared/input/input-with-button.tsx +0 -69
- package/templates/default/src/components/shared/input/input-with-dropdown.tsx +0 -178
- package/templates/default/src/components/shared/input/input-with-prefix.tsx +0 -74
- package/templates/default/src/components/shared/input/label.tsx +0 -46
- package/templates/default/src/components/shared/progress-indicators/progress-circles.tsx +0 -176
- package/templates/default/src/components/shared/progress-indicators/progress-indicators.tsx +0 -86
- package/templates/default/src/components/shared/progress-indicators/simple-circle.tsx +0 -29
- package/templates/default/src/components/shared/radio-buttons/radio-buttons.tsx +0 -125
- package/templates/default/src/components/shared/radio-groups/radio-group-avatar.tsx +0 -62
- package/templates/default/src/components/shared/radio-groups/radio-group-checkbox.tsx +0 -72
- package/templates/default/src/components/shared/radio-groups/radio-group-icon-card.tsx +0 -95
- package/templates/default/src/components/shared/radio-groups/radio-group-icon-simple.tsx +0 -70
- package/templates/default/src/components/shared/radio-groups/radio-group-payment-icon.tsx +0 -71
- package/templates/default/src/components/shared/radio-groups/radio-group-radio-button.tsx +0 -76
- package/templates/default/src/components/shared/radio-groups/radio-groups.tsx +0 -8
- package/templates/default/src/components/shared/select/combobox.tsx +0 -161
- package/templates/default/src/components/shared/select/multi-select.tsx +0 -373
- package/templates/default/src/components/shared/select/popover.tsx +0 -36
- package/templates/default/src/components/shared/select/select-item.tsx +0 -70
- package/templates/default/src/components/shared/select/select-native.tsx +0 -63
- package/templates/default/src/components/shared/select/select.tsx +0 -143
- package/templates/default/src/components/shared/slider/slider.tsx +0 -76
- package/templates/default/src/components/shared/tags/base-components/tag-checkbox.tsx +0 -47
- package/templates/default/src/components/shared/tags/base-components/tag-close-x.tsx +0 -34
- package/templates/default/src/components/shared/tags/tags.tsx +0 -162
- package/templates/default/src/components/shared/textarea/textarea.tsx +0 -82
- package/templates/default/src/components/shared/toggle/toggle.tsx +0 -140
- package/templates/default/src/components/shared/tooltips/tooltips.tsx +0 -140
- package/templates/default/src/components/utils/index.ts +0 -48
- package/templates/default/src/components/utils/isDeepEqual.ts +0 -31
- package/templates/default/src/components/utils/isReactComponent.ts +0 -22
- package/templates/default/src/components/utils/mergeRefs.ts +0 -19
- package/templates/default/src/components/utils/useBreakpoint.ts +0 -36
- package/templates/default/src/components/utils/uuid.ts +0 -9
- package/templates/default/src/hooks/use-resize-observer.tsx +0 -55
- package/templates/default/src/providers/theme.tsx +0 -11
- package/templates/default/src/styles/text-styles.css +0 -177
- package/templates/default/tsconfig.json +0 -27
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ReactNode, Ref } from "react";
|
|
4
|
-
import { Checkbox as AriaCheckbox, type CheckboxProps as AriaCheckboxProps } from "react-aria-components";
|
|
5
|
-
import { cx } from "@/components/utils/cx";
|
|
6
|
-
|
|
7
|
-
export interface CheckboxBaseProps {
|
|
8
|
-
size?: "sm" | "md";
|
|
9
|
-
className?: string;
|
|
10
|
-
isFocused?: boolean;
|
|
11
|
-
isSelected?: boolean;
|
|
12
|
-
isDisabled?: boolean;
|
|
13
|
-
isIndeterminate?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const CheckboxBase = ({ className, isFocused, isSelected, isDisabled, isIndeterminate, size = "sm" }: CheckboxBaseProps) => {
|
|
17
|
-
return (
|
|
18
|
-
<div
|
|
19
|
-
className={cx(
|
|
20
|
-
"flex size-4 shrink-0 cursor-pointer appearance-none items-center justify-center rounded bg-primary ring-1 ring-border-primary ring-inset",
|
|
21
|
-
size === "md" && "size-5 rounded-md",
|
|
22
|
-
(isSelected || isIndeterminate) && "bg-brand-solid ring-bg-brand-solid",
|
|
23
|
-
isDisabled && "cursor-not-allowed bg-disabled_subtle ring-border-disabled",
|
|
24
|
-
isFocused && "outline-2 outline-offset-2 outline-focus-ring",
|
|
25
|
-
className,
|
|
26
|
-
)}
|
|
27
|
-
>
|
|
28
|
-
<svg
|
|
29
|
-
aria-hidden="true"
|
|
30
|
-
viewBox="0 0 14 14"
|
|
31
|
-
fill="none"
|
|
32
|
-
className={cx(
|
|
33
|
-
"pointer-events-none absolute h-3 w-2.5 text-fg-white opacity-0 transition-inherit-all",
|
|
34
|
-
size === "md" && "size-3.5",
|
|
35
|
-
isIndeterminate && "opacity-100",
|
|
36
|
-
isDisabled && "text-fg-disabled_subtle",
|
|
37
|
-
)}
|
|
38
|
-
>
|
|
39
|
-
<path d="M2.91675 7H11.0834" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
40
|
-
</svg>
|
|
41
|
-
|
|
42
|
-
<svg
|
|
43
|
-
aria-hidden="true"
|
|
44
|
-
viewBox="0 0 14 14"
|
|
45
|
-
fill="none"
|
|
46
|
-
className={cx(
|
|
47
|
-
"pointer-events-none absolute size-3 text-fg-white opacity-0 transition-inherit-all",
|
|
48
|
-
size === "md" && "size-3.5",
|
|
49
|
-
isSelected && !isIndeterminate && "opacity-100",
|
|
50
|
-
isDisabled && "text-fg-disabled_subtle",
|
|
51
|
-
)}
|
|
52
|
-
>
|
|
53
|
-
<path d="M11.6666 3.5L5.24992 9.91667L2.33325 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
54
|
-
</svg>
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
CheckboxBase.displayName = "CheckboxBase";
|
|
59
|
-
|
|
60
|
-
interface CheckboxProps extends AriaCheckboxProps {
|
|
61
|
-
ref?: Ref<HTMLLabelElement>;
|
|
62
|
-
size?: "sm" | "md";
|
|
63
|
-
label?: ReactNode;
|
|
64
|
-
hint?: ReactNode;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const Checkbox = ({ label, hint, size = "sm", className, ...ariaCheckboxProps }: CheckboxProps) => {
|
|
68
|
-
const sizes = {
|
|
69
|
-
sm: {
|
|
70
|
-
root: "gap-2",
|
|
71
|
-
textWrapper: "",
|
|
72
|
-
label: "tt-sm-md",
|
|
73
|
-
hint: "tt-sm",
|
|
74
|
-
},
|
|
75
|
-
md: {
|
|
76
|
-
root: "gap-3",
|
|
77
|
-
textWrapper: "gap-0.5",
|
|
78
|
-
label: "tt-md-md",
|
|
79
|
-
hint: "tt-md",
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<AriaCheckbox
|
|
85
|
-
{...ariaCheckboxProps}
|
|
86
|
-
className={(state) =>
|
|
87
|
-
cx(
|
|
88
|
-
"flex items-start",
|
|
89
|
-
state.isDisabled && "cursor-not-allowed",
|
|
90
|
-
sizes[size].root,
|
|
91
|
-
typeof className === "function" ? className(state) : className,
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
>
|
|
95
|
-
{({ isSelected, isIndeterminate, isDisabled, isFocused }) => (
|
|
96
|
-
<>
|
|
97
|
-
<CheckboxBase
|
|
98
|
-
size={size}
|
|
99
|
-
isSelected={isSelected}
|
|
100
|
-
isIndeterminate={isIndeterminate}
|
|
101
|
-
isDisabled={isDisabled}
|
|
102
|
-
isFocused={isFocused}
|
|
103
|
-
className={label || hint ? "mt-0.5" : ""}
|
|
104
|
-
/>
|
|
105
|
-
{(label || hint) && (
|
|
106
|
-
<div className={cx("inline-flex flex-col", sizes[size].textWrapper)}>
|
|
107
|
-
{label && <p className={cx("text-secondary select-none", sizes[size].label)}>{label}</p>}
|
|
108
|
-
{hint && (
|
|
109
|
-
<span className={cx("pointer-events-none text-tertiary", sizes[size].hint)} onClick={(event) => event.stopPropagation()}>
|
|
110
|
-
{hint}
|
|
111
|
-
</span>
|
|
112
|
-
)}
|
|
113
|
-
</div>
|
|
114
|
-
)}
|
|
115
|
-
</>
|
|
116
|
-
)}
|
|
117
|
-
</AriaCheckbox>
|
|
118
|
-
);
|
|
119
|
-
};
|
|
120
|
-
Checkbox.displayName = "Checkbox";
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { FC, ReactNode, RefAttributes } from "react";
|
|
4
|
-
import { DotsVertical } from "@untitledui/icons";
|
|
5
|
-
import type {
|
|
6
|
-
ButtonProps as AriaButtonProps,
|
|
7
|
-
MenuProps as AriaMenuProps,
|
|
8
|
-
PopoverProps as AriaPopoverProps,
|
|
9
|
-
MenuItemProps,
|
|
10
|
-
SeparatorProps,
|
|
11
|
-
} from "react-aria-components";
|
|
12
|
-
import { Button as AriaButton, Header, Menu, MenuItem, MenuSection, MenuTrigger, Popover, Separator } from "react-aria-components";
|
|
13
|
-
import { cx } from "@/components/utils/cx";
|
|
14
|
-
|
|
15
|
-
interface DropdownItemProps extends MenuItemProps {
|
|
16
|
-
/** The label of the item to be displayed. */
|
|
17
|
-
label?: string;
|
|
18
|
-
/** An addon to be displayed on the right side of the item. */
|
|
19
|
-
addon?: string;
|
|
20
|
-
/** If true, the item will not have any styles. */
|
|
21
|
-
unstyled?: boolean;
|
|
22
|
-
/** An icon to be displayed on the left side of the item. */
|
|
23
|
-
icon?: FC<{ className?: string }>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const DropdownItem = ({ label, children, addon, icon: Icon, unstyled, ...props }: DropdownItemProps) => {
|
|
27
|
-
if (unstyled) {
|
|
28
|
-
return <MenuItem id={label} textValue={label} {...props} />;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<MenuItem
|
|
33
|
-
{...props}
|
|
34
|
-
className={(state) =>
|
|
35
|
-
cx(
|
|
36
|
-
"group block cursor-pointer px-1.5 py-px outline-hidden",
|
|
37
|
-
state.isDisabled && "cursor-not-allowed",
|
|
38
|
-
typeof props.className === "function" ? props.className(state) : props.className,
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
>
|
|
42
|
-
{(state) => (
|
|
43
|
-
<div
|
|
44
|
-
className={cx(
|
|
45
|
-
"relative flex items-center rounded-md px-2.5 py-2 outline-focus-ring transition duration-100 ease-linear",
|
|
46
|
-
!state.isDisabled && "group-hover:bg-primary_hover",
|
|
47
|
-
state.isFocused && "bg-primary_hover",
|
|
48
|
-
state.isFocusVisible && "outline-2 -outline-offset-2",
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
51
|
-
{Icon && <Icon className={cx("mr-2 size-4 shrink-0", state.isDisabled ? "text-fg-disabled" : "text-fg-quaternary")} aria-hidden="true" />}
|
|
52
|
-
|
|
53
|
-
<span
|
|
54
|
-
className={cx(
|
|
55
|
-
"grow truncate tt-sm-semi",
|
|
56
|
-
state.isDisabled ? "text-disabled" : "text-secondary",
|
|
57
|
-
state.isFocused && "text-secondary_hover",
|
|
58
|
-
)}
|
|
59
|
-
>
|
|
60
|
-
{label || (typeof children === "function" ? children(state) : children)}
|
|
61
|
-
</span>
|
|
62
|
-
|
|
63
|
-
{addon && (
|
|
64
|
-
<span
|
|
65
|
-
className={cx(
|
|
66
|
-
"ml-3 shrink-0 rounded px-1 py-px tt-xs-md ring-1 ring-border-secondary ring-inset",
|
|
67
|
-
state.isDisabled ? "text-disabled" : "text-quaternary",
|
|
68
|
-
)}
|
|
69
|
-
>
|
|
70
|
-
{addon}
|
|
71
|
-
</span>
|
|
72
|
-
)}
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
</MenuItem>
|
|
76
|
-
);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
interface DropdownMenuProps<T extends object> extends AriaMenuProps<T> {}
|
|
80
|
-
|
|
81
|
-
const DropdownMenu = <T extends object>(props: DropdownMenuProps<T>) => {
|
|
82
|
-
return (
|
|
83
|
-
<Menu
|
|
84
|
-
disallowEmptySelection
|
|
85
|
-
selectionMode="single"
|
|
86
|
-
{...props}
|
|
87
|
-
className={(state) =>
|
|
88
|
-
cx("h-min overflow-y-auto py-1 outline-hidden select-none", typeof props.className === "function" ? props.className(state) : props.className)
|
|
89
|
-
}
|
|
90
|
-
/>
|
|
91
|
-
);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
interface DropdownPopoverProps extends AriaPopoverProps {}
|
|
95
|
-
|
|
96
|
-
const DropdownPopover = (props: DropdownPopoverProps) => {
|
|
97
|
-
return (
|
|
98
|
-
<Popover
|
|
99
|
-
placement="bottom right"
|
|
100
|
-
{...props}
|
|
101
|
-
className={(state) =>
|
|
102
|
-
cx(
|
|
103
|
-
"w-[248px] rounded-lg bg-primary shadow-lg ring-1 ring-border-secondary_alt will-change-transform",
|
|
104
|
-
state.isEntering &&
|
|
105
|
-
"duration-150 ease-out animate-in fade-in zoom-in-95 placement-right:origin-left placement-right:slide-in-from-left-0.5 placement-top:origin-bottom placement-top:slide-in-from-bottom-0.5 placement-bottom:origin-top placement-bottom:slide-in-from-top-0.5",
|
|
106
|
-
state.isExiting &&
|
|
107
|
-
"duration-100 ease-in animate-out fade-out zoom-out-95 placement-right:origin-left placement-right:slide-out-to-left-0.5 placement-top:origin-bottom placement-top:slide-out-to-bottom-0.5 placement-bottom:origin-top placement-bottom:slide-out-to-top-0.5",
|
|
108
|
-
typeof props.className === "function" ? props.className(state) : props.className,
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
/>
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const DropdownSeparator = (props: SeparatorProps) => {
|
|
116
|
-
return <Separator {...props} className={cx("my-1 h-px w-full bg-border-secondary", props.className)} />;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const DropdownDotsButton = (props: AriaButtonProps & RefAttributes<HTMLButtonElement>) => {
|
|
120
|
-
return (
|
|
121
|
-
<AriaButton
|
|
122
|
-
{...props}
|
|
123
|
-
aria-label="Open menu"
|
|
124
|
-
className={(state) =>
|
|
125
|
-
cx(
|
|
126
|
-
"cursor-pointer rounded-md text-fg-quaternary outline-focus-ring transition duration-100 ease-in-out ease-linear",
|
|
127
|
-
(state.isPressed || state.isHovered) && "text-fg-quaternary_hover",
|
|
128
|
-
(state.isPressed || state.isFocused) && "outline-2 outline-offset-2",
|
|
129
|
-
typeof props.className === "function" ? props.className(state) : props.className,
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
>
|
|
133
|
-
<DotsVertical className="size-5 transition-inherit-all" />
|
|
134
|
-
</AriaButton>
|
|
135
|
-
);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export const Dropdown = {
|
|
139
|
-
Root: MenuTrigger,
|
|
140
|
-
Popover: DropdownPopover,
|
|
141
|
-
Menu: DropdownMenu,
|
|
142
|
-
Section: MenuSection,
|
|
143
|
-
SectionHeader: Header,
|
|
144
|
-
Item: DropdownItem,
|
|
145
|
-
Separator: DropdownSeparator,
|
|
146
|
-
DotsButton: DropdownDotsButton,
|
|
147
|
-
};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { DetailedReactHTMLElement, ReactNode } from "react";
|
|
4
|
-
import React, { cloneElement, useRef } from "react";
|
|
5
|
-
import { filterDOMProps } from "@react-aria/utils";
|
|
6
|
-
|
|
7
|
-
interface FileTriggerProps {
|
|
8
|
-
/**
|
|
9
|
-
* Specifies what mime type of files are allowed.
|
|
10
|
-
*/
|
|
11
|
-
acceptedFileTypes?: Array<string>;
|
|
12
|
-
/**
|
|
13
|
-
* Whether multiple files can be selected.
|
|
14
|
-
*/
|
|
15
|
-
allowsMultiple?: boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Specifies the use of a media capture mechanism to capture the media on the spot.
|
|
18
|
-
*/
|
|
19
|
-
defaultCamera?: "user" | "environment";
|
|
20
|
-
/**
|
|
21
|
-
* Handler when a user selects a file.
|
|
22
|
-
*/
|
|
23
|
-
onSelect?: (files: FileList | null) => void;
|
|
24
|
-
/**
|
|
25
|
-
* The children of the component.
|
|
26
|
-
*/
|
|
27
|
-
children: ReactNode;
|
|
28
|
-
/**
|
|
29
|
-
* Enables the selection of directories instead of individual files.
|
|
30
|
-
*/
|
|
31
|
-
acceptDirectory?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// TODO: Make it possible to pass down a ref to the input element.
|
|
35
|
-
/**
|
|
36
|
-
* A FileTrigger allows a user to access the file system with any pressable React Aria or React Spectrum component, or custom components built with usePress.
|
|
37
|
-
*/
|
|
38
|
-
export const FileTrigger = (props: FileTriggerProps) => {
|
|
39
|
-
const { children, onSelect, acceptedFileTypes, allowsMultiple, defaultCamera, acceptDirectory, ...rest } = props;
|
|
40
|
-
|
|
41
|
-
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
42
|
-
const domProps = filterDOMProps(rest);
|
|
43
|
-
|
|
44
|
-
// Make sure that only one child is passed to the component.
|
|
45
|
-
const clonableElement = React.Children.only(children);
|
|
46
|
-
|
|
47
|
-
// Clone the child element and add an `onClick` handler to open the file dialog.
|
|
48
|
-
const mainElement = cloneElement(clonableElement as DetailedReactHTMLElement<any, any>, {
|
|
49
|
-
onClick: () => {
|
|
50
|
-
if (inputRef.current?.value) {
|
|
51
|
-
inputRef.current.value = "";
|
|
52
|
-
}
|
|
53
|
-
inputRef.current?.click();
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<>
|
|
59
|
-
{mainElement}
|
|
60
|
-
<input
|
|
61
|
-
{...domProps}
|
|
62
|
-
type="file"
|
|
63
|
-
ref={inputRef}
|
|
64
|
-
style={{ display: "none" }}
|
|
65
|
-
accept={acceptedFileTypes?.toString()}
|
|
66
|
-
onChange={(e) => onSelect?.(e.target.files)}
|
|
67
|
-
capture={defaultCamera}
|
|
68
|
-
multiple={allowsMultiple}
|
|
69
|
-
// @ts-expect-error
|
|
70
|
-
webkitdirectory={acceptDirectory ? "" : undefined}
|
|
71
|
-
/>
|
|
72
|
-
</>
|
|
73
|
-
);
|
|
74
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ComponentPropsWithRef } from "react";
|
|
4
|
-
import { Form as AriaForm } from "react-aria-components";
|
|
5
|
-
|
|
6
|
-
export const Form = (props: ComponentPropsWithRef<typeof AriaForm>) => {
|
|
7
|
-
return <AriaForm {...props} />;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
Form.displayName = "Form";
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
-
import { createContext, useContext, useId } from "react";
|
|
3
|
-
import { Form as AriaForm } from "react-aria-components";
|
|
4
|
-
import type { Control, FieldPath, FieldValues, UseControllerReturn, UseFormReturn } from "react-hook-form";
|
|
5
|
-
import { FormProvider, useController, useFormContext } from "react-hook-form";
|
|
6
|
-
|
|
7
|
-
interface FormProps<TFieldValues extends FieldValues = FieldValues> extends ComponentPropsWithoutRef<typeof AriaForm> {
|
|
8
|
-
form: UseFormReturn<TFieldValues>;
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface FormFieldProps<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> {
|
|
13
|
-
name: TName;
|
|
14
|
-
control: Control<TFieldValues>;
|
|
15
|
-
children: ReactNode | ((control: UseControllerReturn<TFieldValues, TName>) => ReactNode);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface FormFieldContextValues<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> {
|
|
19
|
-
id: string;
|
|
20
|
-
name: TName;
|
|
21
|
-
control?: UseControllerReturn<TFieldValues, TName>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const FormFieldContext = createContext<FormFieldContextValues>({} as FormFieldContextValues);
|
|
25
|
-
|
|
26
|
-
export const useFormFieldContext = () => {
|
|
27
|
-
const context = useContext(FormFieldContext);
|
|
28
|
-
const { getFieldState, formState } = useFormContext();
|
|
29
|
-
const fieldState = getFieldState(context.name, formState);
|
|
30
|
-
|
|
31
|
-
if (!context) {
|
|
32
|
-
throw new Error("The 'useFormContext' hook must be used within a '<FormField />'");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return { ...context, ...fieldState };
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const HookForm = <TFieldValues extends FieldValues = FieldValues>({ form, ...props }: FormProps<TFieldValues>) => {
|
|
39
|
-
return (
|
|
40
|
-
<FormProvider {...form}>
|
|
41
|
-
<AriaForm {...props} />
|
|
42
|
-
</FormProvider>
|
|
43
|
-
);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
HookForm.displayName = "HookForm";
|
|
47
|
-
|
|
48
|
-
export const FormField = <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({
|
|
49
|
-
children,
|
|
50
|
-
...props
|
|
51
|
-
}: FormFieldProps<TFieldValues, TName>) => {
|
|
52
|
-
const id = "form-item-" + useId();
|
|
53
|
-
const control = useController(props);
|
|
54
|
-
const withValidationBehavior = {
|
|
55
|
-
...control,
|
|
56
|
-
field: {
|
|
57
|
-
...control.field,
|
|
58
|
-
validationBehavior: "aria",
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<FormFieldContext.Provider
|
|
64
|
-
value={{
|
|
65
|
-
id,
|
|
66
|
-
name: props.name,
|
|
67
|
-
control: control as UseControllerReturn<FieldValues, TName>,
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
{children && (typeof children === "function" ? children(withValidationBehavior) : children)}
|
|
71
|
-
</FormFieldContext.Provider>
|
|
72
|
-
);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
FormField.displayName = "FormField";
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ReactNode, Ref } from "react";
|
|
4
|
-
import type { TextProps as AriaTextProps } from "react-aria-components";
|
|
5
|
-
import { Text as AriaText } from "react-aria-components";
|
|
6
|
-
import { cx } from "@/components/utils/cx";
|
|
7
|
-
|
|
8
|
-
interface HintTextProps extends AriaTextProps {
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
isInvalid?: boolean;
|
|
11
|
-
ref?: Ref<HTMLElement>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const HintText = ({ isInvalid, className, ...props }: HintTextProps) => {
|
|
15
|
-
return (
|
|
16
|
-
<AriaText
|
|
17
|
-
{...props}
|
|
18
|
-
slot={isInvalid ? "errorMessage" : "description"}
|
|
19
|
-
className={cx(
|
|
20
|
-
"tt-sm text-tertiary",
|
|
21
|
-
|
|
22
|
-
// Invalid state
|
|
23
|
-
isInvalid && "text-error-primary",
|
|
24
|
-
"group-invalid:text-error-primary",
|
|
25
|
-
|
|
26
|
-
className,
|
|
27
|
-
)}
|
|
28
|
-
/>
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
HintText.displayName = "HintText";
|
|
33
|
-
|
|
34
|
-
export default HintText;
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ComponentType, HTMLAttributes, ReactNode, Ref } from "react";
|
|
4
|
-
import { HelpCircle, InfoCircle } from "@untitledui/icons";
|
|
5
|
-
import type { TextFieldProps as AriaTextFieldProps } from "react-aria-components";
|
|
6
|
-
import { Input as AriaInput, TextField as AriaTextField, Group } from "react-aria-components";
|
|
7
|
-
import HintText from "@/components/shared/input/hint-text";
|
|
8
|
-
import Label from "@/components/shared/input/label";
|
|
9
|
-
import { Tooltip, TooltipTrigger } from "@/components/shared/tooltips/tooltips";
|
|
10
|
-
import { cx, sortCx } from "@/components/utils/cx";
|
|
11
|
-
|
|
12
|
-
export interface InputBaseProps extends TextFieldProps {
|
|
13
|
-
label?: string;
|
|
14
|
-
hint?: ReactNode;
|
|
15
|
-
tooltip?: string;
|
|
16
|
-
size?: "sm" | "md";
|
|
17
|
-
placeholder?: string;
|
|
18
|
-
iconClassName?: string;
|
|
19
|
-
inputClassName?: string;
|
|
20
|
-
wrapperClassName?: string;
|
|
21
|
-
tooltipClassName?: string;
|
|
22
|
-
shortcut?: string | boolean;
|
|
23
|
-
ref?: Ref<HTMLInputElement>;
|
|
24
|
-
groupRef?: Ref<HTMLDivElement>;
|
|
25
|
-
icon?: ComponentType<HTMLAttributes<HTMLOrSVGElement>>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const InputBase = ({ size = "sm", placeholder, icon: Icon, isDisabled, isInvalid, tooltip, shortcut, ref, groupRef, ...props }: InputBaseProps) => {
|
|
29
|
-
// Check if the input has a leading icon or tooltip
|
|
30
|
-
const hasTrailingIcon = tooltip || isInvalid;
|
|
31
|
-
const hasLeadingIcon = Icon;
|
|
32
|
-
|
|
33
|
-
const sizes = sortCx({
|
|
34
|
-
sm: {
|
|
35
|
-
root: cx("px-3 py-2", hasTrailingIcon && "pr-9", hasLeadingIcon && "pl-10"),
|
|
36
|
-
iconLeading: "left-3",
|
|
37
|
-
iconTrailing: "right-3",
|
|
38
|
-
shortcut: "pr-2.5",
|
|
39
|
-
},
|
|
40
|
-
md: {
|
|
41
|
-
root: cx("px-3.5 py-2.5", hasTrailingIcon && "pr-9.5", hasLeadingIcon && "pl-10.5"),
|
|
42
|
-
iconLeading: "left-3.5",
|
|
43
|
-
iconTrailing: "right-3.5",
|
|
44
|
-
shortcut: "pr-3",
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Group
|
|
50
|
-
{...{ isDisabled, isInvalid }}
|
|
51
|
-
ref={groupRef}
|
|
52
|
-
className={({ isFocusWithin, isDisabled, isInvalid }) =>
|
|
53
|
-
cx(
|
|
54
|
-
"relative flex w-full flex-row place-content-center place-items-center rounded-lg bg-primary shadow-xs ring-1 ring-border-primary transition-shadow duration-100 ease-linear ring-inset",
|
|
55
|
-
|
|
56
|
-
isFocusWithin && !isDisabled && "ring-2 ring-border-brand",
|
|
57
|
-
|
|
58
|
-
// Disabled state styles
|
|
59
|
-
isDisabled && "cursor-not-allowed bg-disabled_subtle ring-border-disabled",
|
|
60
|
-
"group-disabled:cursor-not-allowed group-disabled:bg-disabled_subtle group-disabled:ring-border-disabled",
|
|
61
|
-
|
|
62
|
-
// Invalid state styles
|
|
63
|
-
isInvalid && "ring-border-error_subtle",
|
|
64
|
-
"group-invalid:ring-border-error_subtle",
|
|
65
|
-
|
|
66
|
-
// Invalid state with focus-within styles
|
|
67
|
-
isInvalid && isFocusWithin && "ring-2 ring-border-error",
|
|
68
|
-
isFocusWithin && "group-invalid:ring-2 group-invalid:ring-border-error",
|
|
69
|
-
|
|
70
|
-
props.wrapperClassName,
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
>
|
|
74
|
-
{/* Leading icon and Payment icon */}
|
|
75
|
-
{Icon && (
|
|
76
|
-
<Icon
|
|
77
|
-
className={cx(
|
|
78
|
-
"pointer-events-none absolute size-5 text-fg-quaternary",
|
|
79
|
-
isDisabled && "text-fg-disabled",
|
|
80
|
-
sizes[size].iconLeading,
|
|
81
|
-
props.iconClassName,
|
|
82
|
-
)}
|
|
83
|
-
/>
|
|
84
|
-
)}
|
|
85
|
-
|
|
86
|
-
{/* Input field */}
|
|
87
|
-
<AriaInput
|
|
88
|
-
ref={ref}
|
|
89
|
-
placeholder={placeholder}
|
|
90
|
-
className={cx(
|
|
91
|
-
"m-0 w-full bg-transparent tt-md text-primary ring-0 outline-hidden placeholder:text-placeholder autofill:rounded-lg autofill:text-primary",
|
|
92
|
-
isDisabled && "cursor-not-allowed text-disabled",
|
|
93
|
-
sizes[size].root,
|
|
94
|
-
props.inputClassName,
|
|
95
|
-
)}
|
|
96
|
-
/>
|
|
97
|
-
|
|
98
|
-
{/* Tooltip and help icon */}
|
|
99
|
-
{tooltip && !isInvalid && (
|
|
100
|
-
<Tooltip title={tooltip} placement="top">
|
|
101
|
-
<TooltipTrigger
|
|
102
|
-
className={cx(
|
|
103
|
-
"absolute cursor-pointer text-fg-quaternary transition duration-200 hover:text-fg-quaternary_hover focus:text-fg-quaternary_hover",
|
|
104
|
-
sizes[size].iconTrailing,
|
|
105
|
-
props.tooltipClassName,
|
|
106
|
-
)}
|
|
107
|
-
>
|
|
108
|
-
<HelpCircle className="size-4" />
|
|
109
|
-
</TooltipTrigger>
|
|
110
|
-
</Tooltip>
|
|
111
|
-
)}
|
|
112
|
-
|
|
113
|
-
{/* Invalid icon */}
|
|
114
|
-
{isInvalid && (
|
|
115
|
-
<InfoCircle className={cx("pointer-events-none absolute size-4 text-fg-error-secondary", sizes[size].iconTrailing, props.tooltipClassName)} />
|
|
116
|
-
)}
|
|
117
|
-
|
|
118
|
-
{/* Shortcut */}
|
|
119
|
-
{shortcut && (
|
|
120
|
-
<div
|
|
121
|
-
className={cx(
|
|
122
|
-
"absolute inset-y-0.5 right-0.5 z-10 flex items-center rounded-r-[inherit] bg-linear-to-r from-transparent to-bg-primary to-40% pl-8",
|
|
123
|
-
sizes[size].shortcut,
|
|
124
|
-
)}
|
|
125
|
-
>
|
|
126
|
-
<span
|
|
127
|
-
className={cx(
|
|
128
|
-
"pointer-events-none rounded px-1 py-px tt-xs-md text-quaternary ring-1 ring-border-secondary select-none ring-inset",
|
|
129
|
-
isDisabled && "bg-transparent text-disabled",
|
|
130
|
-
)}
|
|
131
|
-
aria-hidden="true"
|
|
132
|
-
>
|
|
133
|
-
{typeof shortcut === "string" ? shortcut : "⌘K"}
|
|
134
|
-
</span>
|
|
135
|
-
</div>
|
|
136
|
-
)}
|
|
137
|
-
</Group>
|
|
138
|
-
);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
InputBase.displayName = "InputBase";
|
|
142
|
-
|
|
143
|
-
interface TextFieldProps extends AriaTextFieldProps {
|
|
144
|
-
ref?: Ref<HTMLDivElement>;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export const TextField = ({ className, ...props }: TextFieldProps) => {
|
|
148
|
-
return (
|
|
149
|
-
<AriaTextField
|
|
150
|
-
{...props}
|
|
151
|
-
className={(state) =>
|
|
152
|
-
cx("group flex h-max w-full flex-col items-start justify-start gap-1.5", typeof className === "function" ? className(state) : className)
|
|
153
|
-
}
|
|
154
|
-
>
|
|
155
|
-
{props.children}
|
|
156
|
-
</AriaTextField>
|
|
157
|
-
);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
TextField.displayName = "TextField";
|
|
161
|
-
|
|
162
|
-
interface InputProps extends InputBaseProps {
|
|
163
|
-
hideRequiredIndicator?: boolean;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export const Input = ({
|
|
167
|
-
size = "sm",
|
|
168
|
-
placeholder = "olivia@untitledui.com",
|
|
169
|
-
icon: Icon,
|
|
170
|
-
label,
|
|
171
|
-
hint,
|
|
172
|
-
hideRequiredIndicator,
|
|
173
|
-
className,
|
|
174
|
-
ref,
|
|
175
|
-
groupRef,
|
|
176
|
-
...props
|
|
177
|
-
}: InputProps) => {
|
|
178
|
-
return (
|
|
179
|
-
<TextField aria-label={!label ? placeholder : undefined} {...props} className={className}>
|
|
180
|
-
{label && <Label isRequired={hideRequiredIndicator ? !hideRequiredIndicator : undefined}>{label}</Label>}
|
|
181
|
-
|
|
182
|
-
<InputBase {...props} {...{ ref, groupRef, size, placeholder, icon: Icon }} />
|
|
183
|
-
|
|
184
|
-
{hint && <HintText>{hint}</HintText>}
|
|
185
|
-
</TextField>
|
|
186
|
-
);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
Input.displayName = "Input";
|