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,134 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useState } from "react";
|
|
4
|
-
import { TextField } from "react-aria-components";
|
|
5
|
-
import { AmexIcon, DiscoverIcon, MastercardIcon, UnionPayIcon, VisaIcon } from "@/components/foundations/payment-icons";
|
|
6
|
-
import type { InputBaseProps } from "@/components/shared/input";
|
|
7
|
-
import { InputBase } from "@/components/shared/input";
|
|
8
|
-
import HintText from "@/components/shared/input/hint-text";
|
|
9
|
-
import Label from "@/components/shared/input/label";
|
|
10
|
-
import { cx } from "@/components/utils/cx";
|
|
11
|
-
|
|
12
|
-
const cardTypes = [
|
|
13
|
-
{
|
|
14
|
-
name: "Visa",
|
|
15
|
-
pattern: /^4[0-9]{3,}$/, // Visa card numbers start with 4 and are 13 or 16 digits long
|
|
16
|
-
card: "visa",
|
|
17
|
-
icon: VisaIcon,
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: "MasterCard",
|
|
21
|
-
pattern: /^5[1-5][0-9]{2,}$/, // MasterCard numbers start with 51-55 and are 16 digits long
|
|
22
|
-
card: "mastercard",
|
|
23
|
-
icon: MastercardIcon,
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
name: "American Express",
|
|
27
|
-
pattern: /^3[47][0-9]{2,}$/, // American Express numbers start with 34 or 37 and are 15 digits long
|
|
28
|
-
card: "amex",
|
|
29
|
-
icon: AmexIcon,
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
name: "Discover",
|
|
33
|
-
pattern: /^6(?:011|5[0-9]{2}|4[4-9][0-9])[0-9]{12}$/, // Discover card numbers start with 6011 or 65 and are 16 digits long
|
|
34
|
-
card: "discover",
|
|
35
|
-
icon: DiscoverIcon,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: "UnionPay",
|
|
39
|
-
pattern: /^(62|88)[0-9]{14,17}$/, // UnionPay card numbers start with 62 or 88 and are between 15-19 digits long
|
|
40
|
-
card: "unionpay",
|
|
41
|
-
icon: UnionPayIcon,
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: "Unknown",
|
|
45
|
-
pattern: /.*/, // Fallback pattern for unknown cards
|
|
46
|
-
card: "unknown",
|
|
47
|
-
icon: MastercardIcon,
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Detect the card type based on the card number.
|
|
53
|
-
* @param number The card number to detect the type for.
|
|
54
|
-
* @returns The matching card type object.
|
|
55
|
-
*/
|
|
56
|
-
const detectCardType = (number: string) => {
|
|
57
|
-
// Remove all spaces
|
|
58
|
-
const sanitizedNumber = number.replace(/\D/g, "");
|
|
59
|
-
|
|
60
|
-
// Find the matching card type
|
|
61
|
-
const card = cardTypes.find((cardType) => cardType.pattern.test(sanitizedNumber));
|
|
62
|
-
|
|
63
|
-
return card || cardTypes[cardTypes.length - 1];
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Format the card number in groups of 4 digits (i.e. 1234 5678 9012 3456).
|
|
68
|
-
*/
|
|
69
|
-
export const formatCardNumber = (number: string) => {
|
|
70
|
-
// Remove non-numeric characters
|
|
71
|
-
const cleaned = number.replace(/\D/g, "");
|
|
72
|
-
|
|
73
|
-
// Format the card number in groups of 4 digits
|
|
74
|
-
const match = cleaned.match(/\d{1,4}/g);
|
|
75
|
-
|
|
76
|
-
if (match) {
|
|
77
|
-
return match.join(" ");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return cleaned;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
interface PaymentInputProps extends Omit<InputBaseProps, "icon"> {}
|
|
84
|
-
|
|
85
|
-
export const PaymentInput = ({ onChange, className, maxLength = 19, label, hint, ...props }: PaymentInputProps) => {
|
|
86
|
-
const [cardNumber, setCardNumber] = useState(formatCardNumber(props.value || props.defaultValue || ""));
|
|
87
|
-
|
|
88
|
-
const handleCardNumberChange = (value: string) => {
|
|
89
|
-
// Remove all non-numeric characters
|
|
90
|
-
value = value.replace(/\D/g, "");
|
|
91
|
-
|
|
92
|
-
// Return if the value is empty
|
|
93
|
-
if (!value) {
|
|
94
|
-
setCardNumber("");
|
|
95
|
-
onChange?.("");
|
|
96
|
-
return "";
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Format the card number in groups of 4 digits
|
|
100
|
-
const formatted = formatCardNumber(value);
|
|
101
|
-
|
|
102
|
-
setCardNumber(formatted);
|
|
103
|
-
onChange?.(value);
|
|
104
|
-
return value;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const card = detectCardType(cardNumber);
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<TextField
|
|
111
|
-
aria-label={!label ? props?.placeholder : undefined}
|
|
112
|
-
{...props}
|
|
113
|
-
value={cardNumber}
|
|
114
|
-
inputMode="numeric"
|
|
115
|
-
maxLength={maxLength}
|
|
116
|
-
onChange={handleCardNumberChange}
|
|
117
|
-
className={(state) =>
|
|
118
|
-
cx("flex h-max w-full flex-col items-start justify-start gap-1.5", typeof className === "function" ? className(state) : className)
|
|
119
|
-
}
|
|
120
|
-
>
|
|
121
|
-
{({ isDisabled, isInvalid, isRequired }) => (
|
|
122
|
-
<>
|
|
123
|
-
{label && <Label {...{ isRequired }}>{label}</Label>}
|
|
124
|
-
|
|
125
|
-
<InputBase {...props} {...{ isDisabled, isInvalid }} icon={card.icon} inputClassName="pl-13" iconClassName="left-2.5 size-max" />
|
|
126
|
-
|
|
127
|
-
{hint && <HintText {...{ isInvalid }}>{hint}</HintText>}
|
|
128
|
-
</>
|
|
129
|
-
)}
|
|
130
|
-
</TextField>
|
|
131
|
-
);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
PaymentInput.displayName = "PaymentInput";
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import React from "react";
|
|
5
|
-
import { Copy01 } from "@untitledui/icons";
|
|
6
|
-
import { TextField } from "react-aria-components";
|
|
7
|
-
import type { CommonProps } from "@/components/shared/buttons/button";
|
|
8
|
-
import Button from "@/components/shared/buttons/button";
|
|
9
|
-
import type { InputBaseProps } from "@/components/shared/input";
|
|
10
|
-
import { InputBase } from "@/components/shared/input";
|
|
11
|
-
import HintText from "@/components/shared/input/hint-text";
|
|
12
|
-
import Label from "@/components/shared/input/label";
|
|
13
|
-
import { cx } from "@/components/utils/cx";
|
|
14
|
-
|
|
15
|
-
interface InputWithButtonProps extends Omit<InputBaseProps, "icon"> {
|
|
16
|
-
buttonText: string;
|
|
17
|
-
onClick?: () => void;
|
|
18
|
-
buttonColor?: CommonProps["color"];
|
|
19
|
-
iconLeading?: CommonProps["iconLeading"];
|
|
20
|
-
iconTrailing?: CommonProps["iconTrailing"];
|
|
21
|
-
hint?: ReactNode;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const InputWithButton = ({
|
|
25
|
-
size = "sm",
|
|
26
|
-
buttonColor = "secondary",
|
|
27
|
-
iconLeading = Copy01,
|
|
28
|
-
onClick,
|
|
29
|
-
className,
|
|
30
|
-
buttonText,
|
|
31
|
-
label,
|
|
32
|
-
hint,
|
|
33
|
-
...props
|
|
34
|
-
}: InputWithButtonProps) => {
|
|
35
|
-
return (
|
|
36
|
-
<TextField
|
|
37
|
-
aria-label={!label ? props?.placeholder : undefined}
|
|
38
|
-
{...props}
|
|
39
|
-
className={(state) =>
|
|
40
|
-
cx("flex h-max w-full flex-col items-start justify-start gap-1.5", typeof className === "function" ? className(state) : className)
|
|
41
|
-
}
|
|
42
|
-
>
|
|
43
|
-
{({ isDisabled, isInvalid, isRequired }) => (
|
|
44
|
-
<>
|
|
45
|
-
{label && <Label {...{ isRequired }}>{label}</Label>}
|
|
46
|
-
|
|
47
|
-
<div className="flex h-max w-full flex-row justify-center">
|
|
48
|
-
<InputBase {...props} {...{ isDisabled, isInvalid }} wrapperClassName="rounded-r-none z-10" />
|
|
49
|
-
|
|
50
|
-
{/* TODO: Take this button out of here and move it to be a prop so the user can fully control the button. */}
|
|
51
|
-
<Button
|
|
52
|
-
onClick={onClick}
|
|
53
|
-
color={buttonColor}
|
|
54
|
-
iconLeading={iconLeading}
|
|
55
|
-
size={size === "sm" ? "md" : "lg"}
|
|
56
|
-
className="-ml-px rounded-l-none shadow-xs! ring-1 ring-border-primary ring-inset focus:z-10"
|
|
57
|
-
>
|
|
58
|
-
{buttonText}
|
|
59
|
-
</Button>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
{hint && <HintText {...{ isInvalid }}>{hint}</HintText>}
|
|
63
|
-
</>
|
|
64
|
-
)}
|
|
65
|
-
</TextField>
|
|
66
|
-
);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
InputWithButton.displayName = "InputWithButton";
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { ChevronDown } from "@untitledui/icons";
|
|
5
|
-
import type { Key } from "react-aria-components";
|
|
6
|
-
import { TextField } from "react-aria-components";
|
|
7
|
-
import type { InputBaseProps } from "@/components/shared/input";
|
|
8
|
-
import { InputBase } from "@/components/shared/input";
|
|
9
|
-
import HintText from "@/components/shared/input/hint-text";
|
|
10
|
-
import Label from "@/components/shared/input/label";
|
|
11
|
-
import { cx, sortCx } from "@/components/utils/cx";
|
|
12
|
-
|
|
13
|
-
interface SelectorComponentProps {
|
|
14
|
-
size: "sm" | "md";
|
|
15
|
-
options: Option[];
|
|
16
|
-
selectedKey?: Key;
|
|
17
|
-
className?: string;
|
|
18
|
-
isInvalid?: boolean;
|
|
19
|
-
selectName?: string;
|
|
20
|
-
isDisabled?: boolean;
|
|
21
|
-
iconClassName?: string;
|
|
22
|
-
onSelectionChange?: (value: Key) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const SelectorComponent = (props: SelectorComponentProps) => {
|
|
26
|
-
return (
|
|
27
|
-
<div className="relative inline-flex h-full items-center">
|
|
28
|
-
<select
|
|
29
|
-
name={props.selectName}
|
|
30
|
-
value={props.selectedKey}
|
|
31
|
-
disabled={props.isDisabled}
|
|
32
|
-
autoComplete="input-dropdown"
|
|
33
|
-
onChange={(e) => props.onSelectionChange?.(e.target.value)}
|
|
34
|
-
className={cx(
|
|
35
|
-
"flex h-full w-min appearance-none items-center gap-1 bg-inherit px-3 py-2 tt-md text-tertiary outline-hidden ring-inset focus:ring-2 focus:ring-border-brand disabled:cursor-not-allowed",
|
|
36
|
-
props.isInvalid && "focus:ring-2 focus:ring-border-error",
|
|
37
|
-
props.isDisabled && "text-disabled",
|
|
38
|
-
props.className,
|
|
39
|
-
)}
|
|
40
|
-
>
|
|
41
|
-
{props.options.map((option) => (
|
|
42
|
-
<option key={option.value} value={option.value}>
|
|
43
|
-
{option.label}
|
|
44
|
-
</option>
|
|
45
|
-
))}
|
|
46
|
-
</select>
|
|
47
|
-
<ChevronDown className={cx("pointer-events-none absolute right-0 size-4 stroke-[2.625px] text-fg-quaternary", props.iconClassName)} />
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
type Option = {
|
|
53
|
-
label: string;
|
|
54
|
-
value: string | number;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
interface InputWithDropdownProps extends Omit<InputBaseProps, "icon"> {
|
|
58
|
-
selectedKey?: Key;
|
|
59
|
-
selectName?: string;
|
|
60
|
-
leadingText?: string;
|
|
61
|
-
leadingOptions?: Option[];
|
|
62
|
-
trailingOptions?: Option[];
|
|
63
|
-
onSelectionChange?: (value: Key) => void;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const InputWithDropdown = ({
|
|
67
|
-
size = "sm",
|
|
68
|
-
leadingOptions,
|
|
69
|
-
trailingOptions,
|
|
70
|
-
leadingText,
|
|
71
|
-
className,
|
|
72
|
-
selectedKey,
|
|
73
|
-
label,
|
|
74
|
-
hint,
|
|
75
|
-
onChange,
|
|
76
|
-
onSelectionChange,
|
|
77
|
-
...props
|
|
78
|
-
}: InputWithDropdownProps) => {
|
|
79
|
-
const hasLeadingDropdown = !!leadingOptions?.length;
|
|
80
|
-
const hasTrailingDropdown = !!trailingOptions?.length;
|
|
81
|
-
|
|
82
|
-
const paddings = sortCx({
|
|
83
|
-
sm: {
|
|
84
|
-
input: cx(hasLeadingDropdown && "px-2.5 pl-2.5", hasTrailingDropdown && (leadingText ? "pr-6! pl-0" : "pr-6! pl-3")),
|
|
85
|
-
leadingText: "pl-3",
|
|
86
|
-
dropdownLeading: "rounded-l-lg py-2 pr-4.5 pl-3",
|
|
87
|
-
dropdownTrailing: "rounded-r-lg py-2 pr-9 pl-3",
|
|
88
|
-
dropdownTrailingIcon: "right-3",
|
|
89
|
-
},
|
|
90
|
-
md: {
|
|
91
|
-
input: cx(hasLeadingDropdown && "px-3 pl-3", hasTrailingDropdown && (leadingText ? "pr-6! pl-0!" : "pr-6! pl-3!")),
|
|
92
|
-
leadingText: "pl-3.5",
|
|
93
|
-
dropdownLeading: "rounded-l-lg py-2.5 pr-4.5 pl-3.5",
|
|
94
|
-
dropdownTrailing: "rounded-r-lg py-2.5 pr-9.5 pl-3.5",
|
|
95
|
-
dropdownTrailingIcon: "right-3.5",
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<TextField
|
|
101
|
-
aria-label={!label ? props?.placeholder : undefined}
|
|
102
|
-
{...props}
|
|
103
|
-
className={(state) =>
|
|
104
|
-
cx("flex h-max w-full flex-col items-start justify-start gap-1.5", typeof className === "function" ? className(state) : className)
|
|
105
|
-
}
|
|
106
|
-
>
|
|
107
|
-
{({ isDisabled, isInvalid, isRequired }) => (
|
|
108
|
-
<>
|
|
109
|
-
{label && <Label {...{ isRequired }}>{label}</Label>}
|
|
110
|
-
|
|
111
|
-
<div
|
|
112
|
-
className={cx(
|
|
113
|
-
"relative flex h-max w-full flex-row justify-start rounded-lg bg-primary shadow-xs ring-1 ring-border-primary transition-all duration-100 ease-linear ring-inset",
|
|
114
|
-
|
|
115
|
-
// Only apply focus ring when child input is focused
|
|
116
|
-
"has-[input:focus]:ring-2 has-[input:focus]:ring-border-brand",
|
|
117
|
-
|
|
118
|
-
isDisabled && "cursor-not-allowed bg-disabled_subtle ring-border-disabled",
|
|
119
|
-
isInvalid && "ring-border-error_subtle has-[input:focus]:ring-border-error",
|
|
120
|
-
)}
|
|
121
|
-
>
|
|
122
|
-
{/* leading dropdown with padding style */}
|
|
123
|
-
{hasLeadingDropdown && (
|
|
124
|
-
<SelectorComponent
|
|
125
|
-
{...{
|
|
126
|
-
size,
|
|
127
|
-
isInvalid,
|
|
128
|
-
isDisabled,
|
|
129
|
-
selectedKey,
|
|
130
|
-
onSelectionChange,
|
|
131
|
-
options: leadingOptions,
|
|
132
|
-
className: paddings[size].dropdownLeading,
|
|
133
|
-
name: props.selectName,
|
|
134
|
-
}}
|
|
135
|
-
/>
|
|
136
|
-
)}
|
|
137
|
-
|
|
138
|
-
{/* leading text if trailing dropdown is applied */}
|
|
139
|
-
{leadingText && (
|
|
140
|
-
<span className={cx("my-auto grow pr-2", paddings[size].leadingText)}>
|
|
141
|
-
<p className={cx("tt-md text-tertiary", isDisabled && "text-disabled")}>{leadingText}</p>
|
|
142
|
-
</span>
|
|
143
|
-
)}
|
|
144
|
-
|
|
145
|
-
<InputBase
|
|
146
|
-
{...props}
|
|
147
|
-
{...{ isDisabled, isInvalid }}
|
|
148
|
-
wrapperClassName={cx("bg-transparent shadow-none! ring-0 focus-within:ring-0")}
|
|
149
|
-
inputClassName={cx(paddings[size].input)}
|
|
150
|
-
tooltipClassName={cx(hasTrailingDropdown && "right-0")}
|
|
151
|
-
/>
|
|
152
|
-
|
|
153
|
-
{/* trailing dropdown with padding style */}
|
|
154
|
-
{hasTrailingDropdown && (
|
|
155
|
-
<SelectorComponent
|
|
156
|
-
{...{
|
|
157
|
-
size,
|
|
158
|
-
isInvalid,
|
|
159
|
-
isDisabled,
|
|
160
|
-
selectedKey,
|
|
161
|
-
onSelectionChange,
|
|
162
|
-
options: trailingOptions,
|
|
163
|
-
iconClassName: paddings[size].dropdownTrailingIcon,
|
|
164
|
-
className: paddings[size].dropdownTrailing,
|
|
165
|
-
name: props.selectName,
|
|
166
|
-
}}
|
|
167
|
-
/>
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
{hint && <HintText {...{ isInvalid }}>{hint}</HintText>}
|
|
172
|
-
</>
|
|
173
|
-
)}
|
|
174
|
-
</TextField>
|
|
175
|
-
);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
InputWithDropdown.displayName = "InputWithDropdown";
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { HTMLAttributes } from "react";
|
|
4
|
-
import type { InputBaseProps } from "@/components/shared/input";
|
|
5
|
-
import { InputBase, TextField } from "@/components/shared/input";
|
|
6
|
-
import HintText from "@/components/shared/input/hint-text";
|
|
7
|
-
import Label from "@/components/shared/input/label";
|
|
8
|
-
import { cx } from "@/components/utils/cx";
|
|
9
|
-
|
|
10
|
-
interface InputPrefixProps extends HTMLAttributes<HTMLDivElement> {
|
|
11
|
-
position?: "leading" | "trailing";
|
|
12
|
-
size?: "sm" | "md";
|
|
13
|
-
isDisabled?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const InputPrefix = ({ position = "leading", size = "sm", isDisabled, children, ...props }: InputPrefixProps) => {
|
|
17
|
-
const styles = {
|
|
18
|
-
sm: "px-3 py-2",
|
|
19
|
-
md: "py-2.5 pl-3.5 pr-3",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div
|
|
24
|
-
{...props}
|
|
25
|
-
className={cx(
|
|
26
|
-
"flex tt-md text-tertiary shadow-xs ring-1 ring-border-primary ring-inset",
|
|
27
|
-
styles[size],
|
|
28
|
-
position === "leading" && "-mr-px rounded-l-lg",
|
|
29
|
-
position === "trailing" && "-ml-px rounded-r-lg",
|
|
30
|
-
|
|
31
|
-
// Disabled state
|
|
32
|
-
isDisabled && "border-disabled bg-disabled_subtle text-tertiary",
|
|
33
|
-
"group-disabled:border-disabled group-disabled:bg-disabled_subtle group-disabled:text-tertiary",
|
|
34
|
-
|
|
35
|
-
props.className,
|
|
36
|
-
)}
|
|
37
|
-
>
|
|
38
|
-
{children}
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
interface InputWithPrefixProps extends Omit<InputBaseProps, "icon"> {
|
|
44
|
-
leadingText?: string;
|
|
45
|
-
trailingText?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const InputWithPrefix = ({ size = "sm", placeholder, leadingText, trailingText, className, label, hint, ...props }: InputWithPrefixProps) => {
|
|
49
|
-
return (
|
|
50
|
-
<TextField aria-label={!label ? placeholder : undefined} {...props} className={className}>
|
|
51
|
-
{label && <Label>{label}</Label>}
|
|
52
|
-
|
|
53
|
-
<div className="flex w-full">
|
|
54
|
-
{leadingText && (
|
|
55
|
-
<InputPrefix position="leading" size={size}>
|
|
56
|
-
{leadingText}
|
|
57
|
-
</InputPrefix>
|
|
58
|
-
)}
|
|
59
|
-
|
|
60
|
-
<InputBase {...props} {...{ size, placeholder }} wrapperClassName={cx(trailingText && "rounded-r-none", leadingText && "rounded-l-none")} />
|
|
61
|
-
|
|
62
|
-
{trailingText && (
|
|
63
|
-
<InputPrefix position="trailing" size={size}>
|
|
64
|
-
{trailingText}
|
|
65
|
-
</InputPrefix>
|
|
66
|
-
)}
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
{hint && <HintText>{hint}</HintText>}
|
|
70
|
-
</TextField>
|
|
71
|
-
);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
InputWithPrefix.displayName = "InputWithPrefix";
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ReactNode, Ref } from "react";
|
|
4
|
-
import { HelpCircle } from "@untitledui/icons";
|
|
5
|
-
import type { LabelProps as AriaLabelProps } from "react-aria-components";
|
|
6
|
-
import { Label as AriaLabel } from "react-aria-components";
|
|
7
|
-
import { Tooltip, TooltipTrigger } from "@/components/shared/tooltips/tooltips";
|
|
8
|
-
import { cx } from "@/components/utils/cx";
|
|
9
|
-
|
|
10
|
-
interface LabelProps extends AriaLabelProps {
|
|
11
|
-
children: ReactNode;
|
|
12
|
-
isRequired?: boolean;
|
|
13
|
-
tooltip?: string;
|
|
14
|
-
tooltipDescription?: string;
|
|
15
|
-
ref?: Ref<HTMLLabelElement>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const Label = ({ isRequired, tooltip, tooltipDescription, className, ...props }: LabelProps) => {
|
|
19
|
-
return (
|
|
20
|
-
<AriaLabel
|
|
21
|
-
// Used for conditionally hiding/showing the label element via CSS:
|
|
22
|
-
// <Input label="Visible only on mobile" className="lg:**:data-label:hidden" />
|
|
23
|
-
// or
|
|
24
|
-
// <Input label="Visible only on mobile" className="lg:label:hidden" />
|
|
25
|
-
data-label="true"
|
|
26
|
-
{...props}
|
|
27
|
-
className={cx("flex w-full cursor-default items-center gap-0.5 tt-sm-md text-secondary", className)}
|
|
28
|
-
>
|
|
29
|
-
{props.children}
|
|
30
|
-
|
|
31
|
-
<span className={cx("hidden text-brand-tertiary", isRequired && "block", typeof isRequired === "undefined" && "group-required:block")}>*</span>
|
|
32
|
-
|
|
33
|
-
{tooltip && (
|
|
34
|
-
<Tooltip title={tooltip} description={tooltipDescription} placement="top">
|
|
35
|
-
<TooltipTrigger className="cursor-pointer text-fg-quaternary transition duration-200 hover:text-fg-quaternary_hover focus:text-fg-quaternary_hover">
|
|
36
|
-
<HelpCircle className="size-4" />
|
|
37
|
-
</TooltipTrigger>
|
|
38
|
-
</Tooltip>
|
|
39
|
-
)}
|
|
40
|
-
</AriaLabel>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
Label.displayName = "Label";
|
|
45
|
-
|
|
46
|
-
export default Label;
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { cx as clx, sortCx } from "@/components/utils/cx";
|
|
4
|
-
|
|
5
|
-
interface ProgressBarProps {
|
|
6
|
-
value: number;
|
|
7
|
-
min?: number;
|
|
8
|
-
max?: number;
|
|
9
|
-
size: "xxs" | "xs" | "sm" | "md" | "lg";
|
|
10
|
-
label?: string;
|
|
11
|
-
valueFormatter?: (value: number, valueInPercentage: number) => string | number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const sizes = sortCx({
|
|
15
|
-
xxs: {
|
|
16
|
-
strokeWidth: 6,
|
|
17
|
-
radius: 29,
|
|
18
|
-
valueClass: "tt-sm-semi text-primary",
|
|
19
|
-
labelClass: "tt-xs-md text-tertiary",
|
|
20
|
-
halfCircleTextPosition: "absolute bottom-0.5 text-center",
|
|
21
|
-
},
|
|
22
|
-
xs: {
|
|
23
|
-
strokeWidth: 16,
|
|
24
|
-
radius: 72,
|
|
25
|
-
valueClass: "td-xs-semi text-primary",
|
|
26
|
-
labelClass: "tt-xs-md text-tertiary",
|
|
27
|
-
halfCircleTextPosition: "absolute bottom-0.5 text-center",
|
|
28
|
-
},
|
|
29
|
-
sm: {
|
|
30
|
-
strokeWidth: 20,
|
|
31
|
-
radius: 90,
|
|
32
|
-
valueClass: "td-sm-semi text-primary",
|
|
33
|
-
labelClass: "tt-xs-md text-tertiary",
|
|
34
|
-
halfCircleTextPosition: "absolute bottom-1 text-center",
|
|
35
|
-
},
|
|
36
|
-
md: {
|
|
37
|
-
strokeWidth: 24,
|
|
38
|
-
radius: 108,
|
|
39
|
-
valueClass: "td-md-semi text-primary",
|
|
40
|
-
labelClass: "tt-sm-md text-tertiary",
|
|
41
|
-
halfCircleTextPosition: "absolute bottom-1 text-center",
|
|
42
|
-
},
|
|
43
|
-
lg: {
|
|
44
|
-
strokeWidth: 28,
|
|
45
|
-
radius: 126,
|
|
46
|
-
valueClass: "td-lg-semi text-primary",
|
|
47
|
-
labelClass: "tt-sm-md text-tertiary",
|
|
48
|
-
halfCircleTextPosition: "absolute bottom-0 text-center",
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
export const ProgressBarCircle = ({ value, min = 0, max = 100, size, label, valueFormatter }: ProgressBarProps) => {
|
|
53
|
-
const percentage = Math.round(((value - min) * 100) / (max - min));
|
|
54
|
-
|
|
55
|
-
const sizeConfig = sizes[size];
|
|
56
|
-
|
|
57
|
-
const { strokeWidth, radius, valueClass, labelClass } = sizeConfig;
|
|
58
|
-
|
|
59
|
-
const diameter = 2 * (radius + strokeWidth / 2);
|
|
60
|
-
const width = diameter;
|
|
61
|
-
const height = diameter;
|
|
62
|
-
const viewBox = `0 0 ${width} ${height}`;
|
|
63
|
-
const cx = diameter / 2;
|
|
64
|
-
const cy = diameter / 2;
|
|
65
|
-
|
|
66
|
-
const textPosition = label ? "absolute text-center" : "absolute text-primary";
|
|
67
|
-
const strokeDashoffset = 100 - percentage;
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<div className="flex flex-col items-center gap-0.5">
|
|
71
|
-
<div role="progressbar" aria-valuenow={value} aria-valuemin={min} aria-valuemax={max} className="relative flex w-max items-center justify-center">
|
|
72
|
-
<svg className="-rotate-90" width={width} height={height} viewBox={viewBox}>
|
|
73
|
-
{/* Background circle */}
|
|
74
|
-
<circle
|
|
75
|
-
className="stroke-bg-quaternary"
|
|
76
|
-
cx={cx}
|
|
77
|
-
cy={cy}
|
|
78
|
-
r={radius}
|
|
79
|
-
fill="none"
|
|
80
|
-
strokeWidth={strokeWidth}
|
|
81
|
-
pathLength="100"
|
|
82
|
-
strokeDasharray="100"
|
|
83
|
-
strokeLinecap="round"
|
|
84
|
-
/>
|
|
85
|
-
|
|
86
|
-
{/* Foreground circle */}
|
|
87
|
-
<circle
|
|
88
|
-
className="stroke-fg-brand-primary"
|
|
89
|
-
cx={cx}
|
|
90
|
-
cy={cy}
|
|
91
|
-
r={radius}
|
|
92
|
-
fill="none"
|
|
93
|
-
strokeWidth={strokeWidth}
|
|
94
|
-
pathLength="100"
|
|
95
|
-
strokeDasharray="100"
|
|
96
|
-
strokeLinecap="round"
|
|
97
|
-
strokeDashoffset={strokeDashoffset}
|
|
98
|
-
/>
|
|
99
|
-
</svg>
|
|
100
|
-
{label && size !== "xxs" ? (
|
|
101
|
-
<div className="absolute text-center">
|
|
102
|
-
<div className={labelClass}>{label}</div>
|
|
103
|
-
<div className={valueClass}>{valueFormatter ? valueFormatter(value, percentage) : `${percentage}%`}</div>
|
|
104
|
-
</div>
|
|
105
|
-
) : (
|
|
106
|
-
<span className={clx(textPosition, valueClass)}>{valueFormatter ? valueFormatter(value, percentage) : `${percentage}%`}</span>
|
|
107
|
-
)}
|
|
108
|
-
</div>
|
|
109
|
-
|
|
110
|
-
{label && size === "xxs" && <div className={labelClass}>{label}</div>}
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
export const ProgressBarHalfCircle = ({ value, min = 0, max = 100, size, label, valueFormatter }: ProgressBarProps) => {
|
|
116
|
-
const percentage = Math.round(((value - min) * 100) / (max - min));
|
|
117
|
-
|
|
118
|
-
const sizeConfig = sizes[size];
|
|
119
|
-
|
|
120
|
-
const { strokeWidth, radius, valueClass, labelClass, halfCircleTextPosition } = sizeConfig;
|
|
121
|
-
|
|
122
|
-
const width = 2 * (radius + strokeWidth / 2);
|
|
123
|
-
const height = radius + strokeWidth;
|
|
124
|
-
const viewBox = `0 0 ${width} ${height}`;
|
|
125
|
-
const cx = "50%";
|
|
126
|
-
const cy = radius + strokeWidth / 2;
|
|
127
|
-
|
|
128
|
-
const strokeDashoffset = -50 - (100 - percentage) / 2;
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<div className="flex flex-col items-center gap-0.5">
|
|
132
|
-
<div role="progressbar" aria-valuenow={value} aria-valuemin={min} aria-valuemax={max} className="relative flex w-max items-center justify-center">
|
|
133
|
-
<svg width={width} height={height} viewBox={viewBox}>
|
|
134
|
-
{/* Background half-circle */}
|
|
135
|
-
<circle
|
|
136
|
-
className="stroke-bg-quaternary"
|
|
137
|
-
cx={cx}
|
|
138
|
-
cy={cy}
|
|
139
|
-
r={radius}
|
|
140
|
-
fill="none"
|
|
141
|
-
strokeWidth={strokeWidth}
|
|
142
|
-
pathLength="100"
|
|
143
|
-
strokeDasharray="100"
|
|
144
|
-
strokeDashoffset="-50"
|
|
145
|
-
strokeLinecap="round"
|
|
146
|
-
/>
|
|
147
|
-
|
|
148
|
-
{/* Foreground half-circle */}
|
|
149
|
-
<circle
|
|
150
|
-
className="origin-center -scale-x-100 stroke-fg-brand-primary"
|
|
151
|
-
cx={cx}
|
|
152
|
-
cy={cy}
|
|
153
|
-
r={radius}
|
|
154
|
-
fill="none"
|
|
155
|
-
strokeWidth={strokeWidth}
|
|
156
|
-
pathLength="100"
|
|
157
|
-
strokeDasharray="100"
|
|
158
|
-
strokeDashoffset={strokeDashoffset}
|
|
159
|
-
strokeLinecap="round"
|
|
160
|
-
/>
|
|
161
|
-
</svg>
|
|
162
|
-
|
|
163
|
-
{label && size !== "xxs" ? (
|
|
164
|
-
<div className={halfCircleTextPosition}>
|
|
165
|
-
<div className={labelClass}>{label}</div>
|
|
166
|
-
<div className={valueClass}>{valueFormatter ? valueFormatter(value, percentage) : `${percentage}%`}</div>
|
|
167
|
-
</div>
|
|
168
|
-
) : (
|
|
169
|
-
<span className={clx(halfCircleTextPosition, valueClass)}>{valueFormatter ? valueFormatter(value, percentage) : `${percentage}%`}</span>
|
|
170
|
-
)}
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
{label && size === "xxs" && <div className={labelClass}>{label}</div>}
|
|
174
|
-
</div>
|
|
175
|
-
);
|
|
176
|
-
};
|