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.
Files changed (101) hide show
  1. package/config/postcss.config.mjs +6 -0
  2. package/{templates/default/src → config}/styles/globals.css +2 -33
  3. package/{templates/default/src → config}/styles/theme.css +31 -33
  4. package/{templates/default/src → config}/styles/typography.css +24 -24
  5. package/dist/index.mjs +54 -15
  6. package/package.json +4 -3
  7. package/templates/default/.prettierrc +0 -10
  8. package/templates/default/README.md +0 -36
  9. package/templates/default/eslint.config.mjs +0 -58
  10. package/templates/default/next.config.ts +0 -6
  11. package/templates/default/package.json +0 -56
  12. package/templates/default/postcss.config.js +0 -5
  13. package/templates/default/public/favicon.ico +0 -0
  14. package/templates/default/public/marketing/smiling-girl.png +0 -0
  15. package/templates/default/public/marketing/spirals.webp +0 -0
  16. package/templates/default/src/app/home-screen.tsx +0 -108
  17. package/templates/default/src/app/layout.tsx +0 -34
  18. package/templates/default/src/app/not-found.tsx +0 -40
  19. package/templates/default/src/app/page.tsx +0 -3
  20. package/templates/default/src/components/foundations/dot-icon.tsx +0 -27
  21. package/templates/default/src/components/foundations/featured-icon/featured-icons.tsx +0 -153
  22. package/templates/default/src/components/foundations/logo/UntitledLogo.tsx +0 -63
  23. package/templates/default/src/components/foundations/logo/UntitledLogoMinimal.tsx +0 -164
  24. package/templates/default/src/components/foundations/payment-icons/amex-icon.tsx +0 -19
  25. package/templates/default/src/components/foundations/payment-icons/apple-pay-icon.tsx +0 -27
  26. package/templates/default/src/components/foundations/payment-icons/discover-icon.tsx +0 -34
  27. package/templates/default/src/components/foundations/payment-icons/index.tsx +0 -10
  28. package/templates/default/src/components/foundations/payment-icons/mastercard-icon.tsx +0 -39
  29. package/templates/default/src/components/foundations/payment-icons/paypal-icon.tsx +0 -45
  30. package/templates/default/src/components/foundations/payment-icons/stripe-icon.tsx +0 -27
  31. package/templates/default/src/components/foundations/payment-icons/union-pay-icon.tsx +0 -37
  32. package/templates/default/src/components/foundations/payment-icons/visa-icon.tsx +0 -27
  33. package/templates/default/src/components/marketing/header-navigation/base-components/nav-menu-item.tsx +0 -41
  34. package/templates/default/src/components/marketing/header-navigation/components/header.tsx +0 -245
  35. package/templates/default/src/components/marketing/header-navigation/dropdown-header-navigation.tsx +0 -53
  36. package/templates/default/src/components/shared/avatar/avatar-label-group.tsx +0 -32
  37. package/templates/default/src/components/shared/avatar/avatar-profile-photo.tsx +0 -84
  38. package/templates/default/src/components/shared/avatar/avatar.tsx +0 -131
  39. package/templates/default/src/components/shared/avatar/base-components/avatar-add-button.tsx +0 -33
  40. package/templates/default/src/components/shared/avatar/base-components/avatar-company-icon.tsx +0 -26
  41. package/templates/default/src/components/shared/avatar/base-components/avatar-online-indicator.tsx +0 -31
  42. package/templates/default/src/components/shared/avatar/base-components/index.ts +0 -4
  43. package/templates/default/src/components/shared/avatar/base-components/verified-tick.tsx +0 -34
  44. package/templates/default/src/components/shared/avatar/utils.ts +0 -12
  45. package/templates/default/src/components/shared/badges/badge-groups.tsx +0 -176
  46. package/templates/default/src/components/shared/badges/badge-types.ts +0 -264
  47. package/templates/default/src/components/shared/badges/badges.tsx +0 -479
  48. package/templates/default/src/components/shared/button-group/button-group.tsx +0 -97
  49. package/templates/default/src/components/shared/buttons/app-store-buttons-outline.tsx +0 -454
  50. package/templates/default/src/components/shared/buttons/app-store-buttons.tsx +0 -806
  51. package/templates/default/src/components/shared/buttons/button-utility.tsx +0 -87
  52. package/templates/default/src/components/shared/buttons/button.tsx +0 -285
  53. package/templates/default/src/components/shared/buttons/close-button.tsx +0 -39
  54. package/templates/default/src/components/shared/buttons/social-button.tsx +0 -135
  55. package/templates/default/src/components/shared/buttons/social-logos.tsx +0 -115
  56. package/templates/default/src/components/shared/checkbox/checkbox.tsx +0 -120
  57. package/templates/default/src/components/shared/dropdown/dropdown.tsx +0 -147
  58. package/templates/default/src/components/shared/file-upload-trigger/file-upload-trigger.tsx +0 -74
  59. package/templates/default/src/components/shared/form/form.tsx +0 -10
  60. package/templates/default/src/components/shared/form/hook-form.tsx +0 -75
  61. package/templates/default/src/components/shared/input/hint-text.tsx +0 -34
  62. package/templates/default/src/components/shared/input/index.tsx +0 -189
  63. package/templates/default/src/components/shared/input/input-payment.tsx +0 -134
  64. package/templates/default/src/components/shared/input/input-with-button.tsx +0 -69
  65. package/templates/default/src/components/shared/input/input-with-dropdown.tsx +0 -178
  66. package/templates/default/src/components/shared/input/input-with-prefix.tsx +0 -74
  67. package/templates/default/src/components/shared/input/label.tsx +0 -46
  68. package/templates/default/src/components/shared/progress-indicators/progress-circles.tsx +0 -176
  69. package/templates/default/src/components/shared/progress-indicators/progress-indicators.tsx +0 -86
  70. package/templates/default/src/components/shared/progress-indicators/simple-circle.tsx +0 -29
  71. package/templates/default/src/components/shared/radio-buttons/radio-buttons.tsx +0 -125
  72. package/templates/default/src/components/shared/radio-groups/radio-group-avatar.tsx +0 -62
  73. package/templates/default/src/components/shared/radio-groups/radio-group-checkbox.tsx +0 -72
  74. package/templates/default/src/components/shared/radio-groups/radio-group-icon-card.tsx +0 -95
  75. package/templates/default/src/components/shared/radio-groups/radio-group-icon-simple.tsx +0 -70
  76. package/templates/default/src/components/shared/radio-groups/radio-group-payment-icon.tsx +0 -71
  77. package/templates/default/src/components/shared/radio-groups/radio-group-radio-button.tsx +0 -76
  78. package/templates/default/src/components/shared/radio-groups/radio-groups.tsx +0 -8
  79. package/templates/default/src/components/shared/select/combobox.tsx +0 -161
  80. package/templates/default/src/components/shared/select/multi-select.tsx +0 -373
  81. package/templates/default/src/components/shared/select/popover.tsx +0 -36
  82. package/templates/default/src/components/shared/select/select-item.tsx +0 -70
  83. package/templates/default/src/components/shared/select/select-native.tsx +0 -63
  84. package/templates/default/src/components/shared/select/select.tsx +0 -143
  85. package/templates/default/src/components/shared/slider/slider.tsx +0 -76
  86. package/templates/default/src/components/shared/tags/base-components/tag-checkbox.tsx +0 -47
  87. package/templates/default/src/components/shared/tags/base-components/tag-close-x.tsx +0 -34
  88. package/templates/default/src/components/shared/tags/tags.tsx +0 -162
  89. package/templates/default/src/components/shared/textarea/textarea.tsx +0 -82
  90. package/templates/default/src/components/shared/toggle/toggle.tsx +0 -140
  91. package/templates/default/src/components/shared/tooltips/tooltips.tsx +0 -140
  92. package/templates/default/src/components/utils/index.ts +0 -48
  93. package/templates/default/src/components/utils/isDeepEqual.ts +0 -31
  94. package/templates/default/src/components/utils/isReactComponent.ts +0 -22
  95. package/templates/default/src/components/utils/mergeRefs.ts +0 -19
  96. package/templates/default/src/components/utils/useBreakpoint.ts +0 -36
  97. package/templates/default/src/components/utils/uuid.ts +0 -9
  98. package/templates/default/src/hooks/use-resize-observer.tsx +0 -55
  99. package/templates/default/src/providers/theme.tsx +0 -11
  100. package/templates/default/src/styles/text-styles.css +0 -177
  101. 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";