keystone-design-bootstrap 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -0
- package/package.json +59 -0
- package/src/contexts/ThemeContext.tsx +34 -0
- package/src/contexts/index.ts +1 -0
- package/src/design_system/elements/IconComponent.tsx +98 -0
- package/src/design_system/elements/avatar/avatar-label-group.tsx +30 -0
- package/src/design_system/elements/avatar/avatar-profile-photo.tsx +125 -0
- package/src/design_system/elements/avatar/avatar.tsx +131 -0
- package/src/design_system/elements/avatar/base-components/avatar-add-button.tsx +34 -0
- package/src/design_system/elements/avatar/base-components/avatar-company-icon.tsx +26 -0
- package/src/design_system/elements/avatar/base-components/avatar-online-indicator.tsx +31 -0
- package/src/design_system/elements/avatar/base-components/index.tsx +4 -0
- package/src/design_system/elements/avatar/base-components/verified-tick.tsx +34 -0
- package/src/design_system/elements/avatar/utils.ts +12 -0
- package/src/design_system/elements/badges/avatar.tsx +132 -0
- package/src/design_system/elements/badges/badge-groups.tsx +176 -0
- package/src/design_system/elements/badges/badge-types.ts +266 -0
- package/src/design_system/elements/badges/badges.tsx +430 -0
- package/src/design_system/elements/breadcrumb/Breadcrumb.tsx +33 -0
- package/src/design_system/elements/button-group/button-group.tsx +106 -0
- package/src/design_system/elements/buttons/app-store-buttons-outline.tsx +378 -0
- package/src/design_system/elements/buttons/app-store-buttons.tsx +567 -0
- package/src/design_system/elements/buttons/button-utility.tsx +116 -0
- package/src/design_system/elements/buttons/button.aman.tsx +174 -0
- package/src/design_system/elements/buttons/button.tsx +271 -0
- package/src/design_system/elements/buttons/close-button.tsx +42 -0
- package/src/design_system/elements/buttons/round-button.tsx +29 -0
- package/src/design_system/elements/buttons/social-button.tsx +148 -0
- package/src/design_system/elements/buttons/social-logos.tsx +115 -0
- package/src/design_system/elements/carousel/carousel-base.tsx +308 -0
- package/src/design_system/elements/carousel/carousel.tsx +308 -0
- package/src/design_system/elements/checkbox/checkbox.tsx +120 -0
- package/src/design_system/elements/date-picker/calendar.tsx +101 -0
- package/src/design_system/elements/date-picker/cell.tsx +106 -0
- package/src/design_system/elements/date-picker/date-input.tsx +32 -0
- package/src/design_system/elements/date-picker/date-picker.tsx +86 -0
- package/src/design_system/elements/date-picker/date-range-picker.tsx +163 -0
- package/src/design_system/elements/date-picker/range-calendar.tsx +161 -0
- package/src/design_system/elements/date-picker/range-preset.tsx +28 -0
- package/src/design_system/elements/featured-icon/featured-icon.tsx +154 -0
- package/src/design_system/elements/form/form.tsx +10 -0
- package/src/design_system/elements/form/hook-form.tsx +75 -0
- package/src/design_system/elements/hint-text/hint-text.tsx +33 -0
- package/src/design_system/elements/index.tsx +158 -0
- package/src/design_system/elements/input/hint-text.tsx +33 -0
- package/src/design_system/elements/input/input-group.tsx +133 -0
- package/src/design_system/elements/input/input.aman.tsx +172 -0
- package/src/design_system/elements/input/input.tsx +271 -0
- package/src/design_system/elements/input/label.tsx +50 -0
- package/src/design_system/elements/label/label.tsx +50 -0
- package/src/design_system/elements/loading-indicator/loading-indicator.tsx +123 -0
- package/src/design_system/elements/map/GoogleMap.tsx +286 -0
- package/src/design_system/elements/markdown-renderer/MarkdownRenderer.tsx +155 -0
- package/src/design_system/elements/modals/modal.tsx +41 -0
- package/src/design_system/elements/pagination/pagination-base.tsx +378 -0
- package/src/design_system/elements/pagination/pagination-dot.tsx +54 -0
- package/src/design_system/elements/pagination/pagination-line.tsx +50 -0
- package/src/design_system/elements/pagination/pagination.tsx +330 -0
- package/src/design_system/elements/photo-fallback/photo-fallback.tsx +143 -0
- package/src/design_system/elements/progress-indicators/progress-circles.tsx +176 -0
- package/src/design_system/elements/progress-indicators/progress-indicators.tsx +123 -0
- package/src/design_system/elements/progress-indicators/simple-circle.tsx +29 -0
- package/src/design_system/elements/radio-buttons/radio-buttons.tsx +129 -0
- package/src/design_system/elements/rating/rating-badge.tsx +144 -0
- package/src/design_system/elements/rating/rating-stars.tsx +77 -0
- package/src/design_system/elements/select/combobox.tsx +152 -0
- package/src/design_system/elements/select/multi-select.tsx +363 -0
- package/src/design_system/elements/select/popover.tsx +34 -0
- package/src/design_system/elements/select/select-item.tsx +97 -0
- package/src/design_system/elements/select/select-native.tsx +69 -0
- package/src/design_system/elements/select/select.aman.tsx +75 -0
- package/src/design_system/elements/select/select.tsx +146 -0
- package/src/design_system/elements/shared-assets/credit-card/credit-card.tsx +237 -0
- package/src/design_system/elements/shared-assets/credit-card/icons.tsx +75 -0
- package/src/design_system/elements/shared-assets/iphone-mockup.tsx +172 -0
- package/src/design_system/elements/shared-assets/section-divider.tsx +12 -0
- package/src/design_system/elements/slideout-menus/slideout-menu.tsx +122 -0
- package/src/design_system/elements/tabs/tabs.tsx +225 -0
- package/src/design_system/elements/tags/base-components/tag-checkbox.tsx +45 -0
- package/src/design_system/elements/tags/base-components/tag-close-x.tsx +34 -0
- package/src/design_system/elements/tags/tags.tsx +176 -0
- package/src/design_system/elements/textarea/textarea.aman.tsx +52 -0
- package/src/design_system/elements/textarea/textarea.tsx +111 -0
- package/src/design_system/elements/toggle/toggle.tsx +140 -0
- package/src/design_system/elements/tooltip/tooltip.tsx +109 -0
- package/src/design_system/hooks/use-breakpoint.ts +37 -0
- package/src/design_system/hooks/use-resize-observer.ts +68 -0
- package/src/design_system/logo/keystone-logo-minimal.tsx +93 -0
- package/src/design_system/logo/keystone-logo.tsx +22 -0
- package/src/design_system/sections/about-home.aman.tsx +85 -0
- package/src/design_system/sections/about-home.tsx +115 -0
- package/src/design_system/sections/blog-cards.tsx +848 -0
- package/src/design_system/sections/blog-gallery.aman.tsx +77 -0
- package/src/design_system/sections/blog-gallery.tsx +204 -0
- package/src/design_system/sections/blog-home.aman.tsx +84 -0
- package/src/design_system/sections/blog-home.tsx +153 -0
- package/src/design_system/sections/blog-post.aman.tsx +74 -0
- package/src/design_system/sections/blog-post.tsx +301 -0
- package/src/design_system/sections/blog-section.aman.tsx +101 -0
- package/src/design_system/sections/blog-section.tsx +179 -0
- package/src/design_system/sections/contact-home.tsx +25 -0
- package/src/design_system/sections/contact-section.aman.tsx +173 -0
- package/src/design_system/sections/contact-section.tsx +143 -0
- package/src/design_system/sections/faq-grid.aman.tsx +79 -0
- package/src/design_system/sections/faq-grid.tsx +102 -0
- package/src/design_system/sections/faq-home.aman.tsx +92 -0
- package/src/design_system/sections/faq-home.tsx +134 -0
- package/src/design_system/sections/feature-tab.tsx +43 -0
- package/src/design_system/sections/feature-text.tsx +284 -0
- package/src/design_system/sections/footer-home.aman.tsx +62 -0
- package/src/design_system/sections/footer-home.tsx +259 -0
- package/src/design_system/sections/generic-header-component.tsx +103 -0
- package/src/design_system/sections/header-navigation.aman.tsx +360 -0
- package/src/design_system/sections/header-navigation.tsx +334 -0
- package/src/design_system/sections/hero-faq.aman.tsx +38 -0
- package/src/design_system/sections/hero-faq.tsx +55 -0
- package/src/design_system/sections/hero-generic-text.aman.tsx +49 -0
- package/src/design_system/sections/hero-generic-text.tsx +51 -0
- package/src/design_system/sections/hero-home.aman.tsx +84 -0
- package/src/design_system/sections/hero-home.tsx +246 -0
- package/src/design_system/sections/hero-location-detail.aman.tsx +33 -0
- package/src/design_system/sections/hero-location-detail.tsx +72 -0
- package/src/design_system/sections/hero-service-detail.aman.tsx +53 -0
- package/src/design_system/sections/hero-service-detail.tsx +51 -0
- package/src/design_system/sections/hero-social-media.aman.tsx +42 -0
- package/src/design_system/sections/hero-social-media.tsx +35 -0
- package/src/design_system/sections/hero-testimonials.aman.tsx +38 -0
- package/src/design_system/sections/hero-testimonials.tsx +55 -0
- package/src/design_system/sections/home-hero-component.tsx +228 -0
- package/src/design_system/sections/index.tsx +131 -0
- package/src/design_system/sections/job-gallery.aman.tsx +91 -0
- package/src/design_system/sections/job-gallery.tsx +183 -0
- package/src/design_system/sections/location-details-section.aman.tsx +179 -0
- package/src/design_system/sections/location-details-section.tsx +196 -0
- package/src/design_system/sections/location-grid.aman.tsx +76 -0
- package/src/design_system/sections/location-grid.tsx +123 -0
- package/src/design_system/sections/services-grid.aman.tsx +85 -0
- package/src/design_system/sections/services-grid.tsx +104 -0
- package/src/design_system/sections/services-home.aman.tsx +78 -0
- package/src/design_system/sections/services-home.tsx +131 -0
- package/src/design_system/sections/social-media-grid.aman.tsx +132 -0
- package/src/design_system/sections/social-media-grid.tsx +189 -0
- package/src/design_system/sections/statistics-section.aman.tsx +79 -0
- package/src/design_system/sections/statistics-section.tsx +97 -0
- package/src/design_system/sections/team-grid.aman.tsx +85 -0
- package/src/design_system/sections/team-grid.tsx +88 -0
- package/src/design_system/sections/testimonials-home.aman.tsx +113 -0
- package/src/design_system/sections/testimonials-home.tsx +90 -0
- package/src/design_system/sections/values-section.aman.tsx +73 -0
- package/src/design_system/sections/values-section.tsx +128 -0
- package/src/design_system/utils/icon-mapping.tsx +28 -0
- package/src/index.ts +7 -0
- package/src/lib/component-registry.ts +53 -0
- package/src/lib/hooks/index.ts +8 -0
- package/src/lib/hooks/use-breakpoint.ts +37 -0
- package/src/lib/hooks/use-clipboard.ts +79 -0
- package/src/lib/hooks/use-resize-observer.ts +68 -0
- package/src/lib/server-api.ts +115 -0
- package/src/styles/style-overrides.aman.css +101 -0
- package/src/styles/theme.css +224 -0
- package/src/styles/typography.css +430 -0
- package/src/themes/index.ts +23 -0
- package/src/types/api/blog-post.ts +53 -0
- package/src/types/api/company-information.ts +44 -0
- package/src/types/api/contact.ts +63 -0
- package/src/types/api/faq.ts +37 -0
- package/src/types/api/job-posting.ts +34 -0
- package/src/types/api/location.ts +36 -0
- package/src/types/api/photos.ts +28 -0
- package/src/types/api/service.ts +37 -0
- package/src/types/api/social-post.ts +28 -0
- package/src/types/api/team-member.ts +29 -0
- package/src/types/api/testimonial.ts +29 -0
- package/src/types/api/website-photos.ts +22 -0
- package/src/types/config.ts +21 -0
- package/src/types/index.ts +21 -0
- package/src/utils/countries.tsx +1351 -0
- package/src/utils/cx.ts +25 -0
- package/src/utils/gradient-placeholder.ts +59 -0
- package/src/utils/is-react-component.ts +33 -0
- package/src/utils/markdown-toc.ts +54 -0
- package/src/utils/photo-helpers.ts +94 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type ComponentType, type HTMLAttributes, type ReactNode, type Ref, createContext, useContext } from "react";
|
|
4
|
+
import { HelpCircle, InfoCircle } from "@untitledui/icons";
|
|
5
|
+
import type { InputProps as AriaInputProps, TextFieldProps as AriaTextFieldProps } from "react-aria-components";
|
|
6
|
+
import { Group as AriaGroup, Input as AriaInput, TextField as AriaTextField } from "react-aria-components";
|
|
7
|
+
import { HintText } from './hint-text';
|
|
8
|
+
import { Label } from './label';
|
|
9
|
+
import { Tooltip, TooltipTrigger } from '../tooltip/tooltip';
|
|
10
|
+
import { cx, sortCx } from '../../../utils/cx';
|
|
11
|
+
|
|
12
|
+
export interface InputBaseProps extends TextFieldProps {
|
|
13
|
+
/** Tooltip message on hover. */
|
|
14
|
+
tooltip?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Input size.
|
|
17
|
+
* @default "sm"
|
|
18
|
+
*/
|
|
19
|
+
size?: "sm" | "md";
|
|
20
|
+
/** Placeholder text. */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Class name for the icon. */
|
|
23
|
+
iconClassName?: string;
|
|
24
|
+
/** Class name for the input. */
|
|
25
|
+
inputClassName?: string;
|
|
26
|
+
/** Class name for the input wrapper. */
|
|
27
|
+
wrapperClassName?: string;
|
|
28
|
+
/** Class name for the tooltip. */
|
|
29
|
+
tooltipClassName?: string;
|
|
30
|
+
/** Keyboard shortcut to display. */
|
|
31
|
+
shortcut?: string | boolean;
|
|
32
|
+
ref?: Ref<HTMLInputElement>;
|
|
33
|
+
groupRef?: Ref<HTMLDivElement>;
|
|
34
|
+
/** Icon component to display on the left side of the input. */
|
|
35
|
+
icon?: ComponentType<HTMLAttributes<HTMLOrSVGElement>>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const InputBase = ({
|
|
39
|
+
ref,
|
|
40
|
+
tooltip,
|
|
41
|
+
shortcut,
|
|
42
|
+
groupRef,
|
|
43
|
+
size = "sm",
|
|
44
|
+
isInvalid,
|
|
45
|
+
isDisabled,
|
|
46
|
+
icon: Icon,
|
|
47
|
+
placeholder,
|
|
48
|
+
wrapperClassName,
|
|
49
|
+
tooltipClassName,
|
|
50
|
+
inputClassName,
|
|
51
|
+
iconClassName,
|
|
52
|
+
// Omit this prop to avoid invalid HTML attribute warning
|
|
53
|
+
isRequired: _isRequired,
|
|
54
|
+
...inputProps
|
|
55
|
+
}: Omit<InputBaseProps, "label" | "hint">) => {
|
|
56
|
+
// Check if the input has a leading icon or tooltip
|
|
57
|
+
const hasTrailingIcon = tooltip || isInvalid;
|
|
58
|
+
const hasLeadingIcon = Icon;
|
|
59
|
+
|
|
60
|
+
// If the input is inside a `TextFieldContext`, use its context to simplify applying styles
|
|
61
|
+
const context = useContext(TextFieldContext);
|
|
62
|
+
|
|
63
|
+
const inputSize = context?.size || size;
|
|
64
|
+
|
|
65
|
+
const sizes = sortCx({
|
|
66
|
+
sm: {
|
|
67
|
+
root: cx("px-3 py-2", hasTrailingIcon && "pr-9", hasLeadingIcon && "pl-10"),
|
|
68
|
+
iconLeading: "left-3",
|
|
69
|
+
iconTrailing: "right-3",
|
|
70
|
+
shortcut: "pr-2.5",
|
|
71
|
+
},
|
|
72
|
+
md: {
|
|
73
|
+
root: cx("px-3.5 py-2.5", hasTrailingIcon && "pr-9.5", hasLeadingIcon && "pl-10.5"),
|
|
74
|
+
iconLeading: "left-3.5",
|
|
75
|
+
iconTrailing: "right-3.5",
|
|
76
|
+
shortcut: "pr-3",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<AriaGroup
|
|
82
|
+
{...{ isDisabled, isInvalid }}
|
|
83
|
+
ref={groupRef}
|
|
84
|
+
className={({ isFocusWithin, isDisabled, isInvalid }) =>
|
|
85
|
+
cx(
|
|
86
|
+
"relative flex w-full flex-row place-content-center place-items-center rounded-lg bg-primary shadow-xs ring-1 ring-primary transition-shadow duration-100 ease-linear ring-inset",
|
|
87
|
+
|
|
88
|
+
isFocusWithin && !isDisabled && "ring-2 ring-brand",
|
|
89
|
+
|
|
90
|
+
// Disabled state styles
|
|
91
|
+
isDisabled && "cursor-not-allowed bg-disabled_subtle ring-disabled",
|
|
92
|
+
"group-disabled:cursor-not-allowed group-disabled:bg-disabled_subtle group-disabled:ring-disabled",
|
|
93
|
+
|
|
94
|
+
// Invalid state styles
|
|
95
|
+
isInvalid && "ring-error_subtle",
|
|
96
|
+
"group-invalid:ring-error_subtle",
|
|
97
|
+
|
|
98
|
+
// Invalid state with focus-within styles
|
|
99
|
+
isInvalid && isFocusWithin && "ring-2 ring-error",
|
|
100
|
+
isFocusWithin && "group-invalid:ring-2 group-invalid:ring-error",
|
|
101
|
+
|
|
102
|
+
context?.wrapperClassName,
|
|
103
|
+
wrapperClassName,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
>
|
|
107
|
+
{/* Leading icon and Payment icon */}
|
|
108
|
+
{Icon && (
|
|
109
|
+
<Icon
|
|
110
|
+
className={cx(
|
|
111
|
+
"pointer-events-none absolute size-5 text-fg-quaternary",
|
|
112
|
+
isDisabled && "text-fg-disabled",
|
|
113
|
+
sizes[inputSize].iconLeading,
|
|
114
|
+
context?.iconClassName,
|
|
115
|
+
iconClassName,
|
|
116
|
+
)}
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{/* Input field */}
|
|
121
|
+
<AriaInput
|
|
122
|
+
{...(inputProps as AriaInputProps)}
|
|
123
|
+
ref={ref}
|
|
124
|
+
placeholder={placeholder}
|
|
125
|
+
className={cx(
|
|
126
|
+
"m-0 w-full bg-transparent text-md text-primary ring-0 outline-hidden placeholder:text-placeholder autofill:rounded-lg autofill:text-primary",
|
|
127
|
+
isDisabled && "cursor-not-allowed text-disabled",
|
|
128
|
+
sizes[inputSize].root,
|
|
129
|
+
context?.inputClassName,
|
|
130
|
+
inputClassName,
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
{/* Tooltip and help icon */}
|
|
135
|
+
{tooltip && !isInvalid && (
|
|
136
|
+
<Tooltip title={tooltip} placement="top">
|
|
137
|
+
<TooltipTrigger
|
|
138
|
+
className={cx(
|
|
139
|
+
"absolute cursor-pointer text-fg-quaternary transition duration-200 hover:text-fg-quaternary_hover focus:text-fg-quaternary_hover",
|
|
140
|
+
sizes[inputSize].iconTrailing,
|
|
141
|
+
context?.tooltipClassName,
|
|
142
|
+
tooltipClassName,
|
|
143
|
+
)}
|
|
144
|
+
>
|
|
145
|
+
<HelpCircle className="size-4" />
|
|
146
|
+
</TooltipTrigger>
|
|
147
|
+
</Tooltip>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Invalid icon */}
|
|
151
|
+
{isInvalid && (
|
|
152
|
+
<InfoCircle
|
|
153
|
+
className={cx(
|
|
154
|
+
"pointer-events-none absolute size-4 text-fg-error-secondary",
|
|
155
|
+
sizes[inputSize].iconTrailing,
|
|
156
|
+
context?.tooltipClassName,
|
|
157
|
+
tooltipClassName,
|
|
158
|
+
)}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* Shortcut */}
|
|
163
|
+
{shortcut && (
|
|
164
|
+
<div
|
|
165
|
+
className={cx(
|
|
166
|
+
"pointer-events-none 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",
|
|
167
|
+
sizes[inputSize].shortcut,
|
|
168
|
+
)}
|
|
169
|
+
>
|
|
170
|
+
<span
|
|
171
|
+
className={cx(
|
|
172
|
+
"pointer-events-none rounded px-1 py-px text-xs font-medium text-quaternary ring-1 ring-secondary select-none ring-inset",
|
|
173
|
+
isDisabled && "bg-transparent text-disabled",
|
|
174
|
+
)}
|
|
175
|
+
aria-hidden="true"
|
|
176
|
+
>
|
|
177
|
+
{typeof shortcut === "string" ? shortcut : "⌘K"}
|
|
178
|
+
</span>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</AriaGroup>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
InputBase.displayName = "InputBase";
|
|
186
|
+
|
|
187
|
+
interface BaseProps {
|
|
188
|
+
/** Label text for the input */
|
|
189
|
+
label?: string;
|
|
190
|
+
/** Helper text displayed below the input */
|
|
191
|
+
hint?: ReactNode;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface TextFieldProps
|
|
195
|
+
extends BaseProps,
|
|
196
|
+
AriaTextFieldProps,
|
|
197
|
+
Pick<InputBaseProps, "size" | "wrapperClassName" | "inputClassName" | "iconClassName" | "tooltipClassName"> {
|
|
198
|
+
ref?: Ref<HTMLDivElement>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const TextFieldContext = createContext<TextFieldProps>({});
|
|
202
|
+
|
|
203
|
+
export const TextField = ({ className, ...props }: TextFieldProps) => {
|
|
204
|
+
return (
|
|
205
|
+
<TextFieldContext.Provider value={props}>
|
|
206
|
+
<AriaTextField
|
|
207
|
+
{...props}
|
|
208
|
+
data-input-wrapper
|
|
209
|
+
className={(state) =>
|
|
210
|
+
cx("group flex h-max w-full flex-col items-start justify-start gap-1.5", typeof className === "function" ? className(state) : className)
|
|
211
|
+
}
|
|
212
|
+
/>
|
|
213
|
+
</TextFieldContext.Provider>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
TextField.displayName = "TextField";
|
|
218
|
+
|
|
219
|
+
interface InputProps extends InputBaseProps, BaseProps {
|
|
220
|
+
/** Whether to hide required indicator from label */
|
|
221
|
+
hideRequiredIndicator?: boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const Input = ({
|
|
225
|
+
size = "sm",
|
|
226
|
+
placeholder,
|
|
227
|
+
icon: Icon,
|
|
228
|
+
label,
|
|
229
|
+
hint,
|
|
230
|
+
shortcut,
|
|
231
|
+
hideRequiredIndicator,
|
|
232
|
+
className,
|
|
233
|
+
ref,
|
|
234
|
+
groupRef,
|
|
235
|
+
tooltip,
|
|
236
|
+
iconClassName,
|
|
237
|
+
inputClassName,
|
|
238
|
+
wrapperClassName,
|
|
239
|
+
tooltipClassName,
|
|
240
|
+
...props
|
|
241
|
+
}: InputProps) => {
|
|
242
|
+
return (
|
|
243
|
+
<TextField aria-label={!label ? placeholder : undefined} {...props} className={className}>
|
|
244
|
+
{({ isRequired, isInvalid }) => (
|
|
245
|
+
<>
|
|
246
|
+
{label && <Label isRequired={hideRequiredIndicator ? !hideRequiredIndicator : isRequired}>{label}</Label>}
|
|
247
|
+
|
|
248
|
+
<InputBase
|
|
249
|
+
{...{
|
|
250
|
+
ref,
|
|
251
|
+
groupRef,
|
|
252
|
+
size,
|
|
253
|
+
placeholder,
|
|
254
|
+
icon: Icon,
|
|
255
|
+
shortcut,
|
|
256
|
+
iconClassName,
|
|
257
|
+
inputClassName,
|
|
258
|
+
wrapperClassName,
|
|
259
|
+
tooltipClassName,
|
|
260
|
+
tooltip,
|
|
261
|
+
}}
|
|
262
|
+
/>
|
|
263
|
+
|
|
264
|
+
{hint && <HintText isInvalid={isInvalid}>{hint}</HintText>}
|
|
265
|
+
</>
|
|
266
|
+
)}
|
|
267
|
+
</TextField>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
Input.displayName = "Input";
|
|
@@ -0,0 +1,50 @@
|
|
|
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 '../tooltip/tooltip';
|
|
8
|
+
import { cx } from '../../../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
|
+
export 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 cursor-default items-center gap-0.5 text-sm font-medium 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
|
|
36
|
+
// `TooltipTrigger` inherits the disabled state from the parent form field
|
|
37
|
+
// but we don't that. We want the tooltip be enabled even if the parent
|
|
38
|
+
// field is disabled.
|
|
39
|
+
isDisabled={false}
|
|
40
|
+
className="cursor-pointer text-fg-quaternary transition duration-200 hover:text-fg-quaternary_hover focus:text-fg-quaternary_hover"
|
|
41
|
+
>
|
|
42
|
+
<HelpCircle className="size-4" />
|
|
43
|
+
</TooltipTrigger>
|
|
44
|
+
</Tooltip>
|
|
45
|
+
)}
|
|
46
|
+
</AriaLabel>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
Label.displayName = "Label";
|
|
@@ -0,0 +1,50 @@
|
|
|
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 '../tooltip/tooltip';
|
|
8
|
+
import { cx } from '../../../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
|
+
export 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 cursor-default items-center gap-0.5 text-sm font-medium 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
|
|
36
|
+
// `TooltipTrigger` inherits the disabled state from the parent form field
|
|
37
|
+
// but we don't that. We want the tooltip be enabled even if the parent
|
|
38
|
+
// field is disabled.
|
|
39
|
+
isDisabled={false}
|
|
40
|
+
className="cursor-pointer text-fg-quaternary transition duration-200 hover:text-fg-quaternary_hover focus:text-fg-quaternary_hover"
|
|
41
|
+
>
|
|
42
|
+
<HelpCircle className="size-4" />
|
|
43
|
+
</TooltipTrigger>
|
|
44
|
+
</Tooltip>
|
|
45
|
+
)}
|
|
46
|
+
</AriaLabel>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
Label.displayName = "Label";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cx } from '../../../utils/cx';
|
|
4
|
+
|
|
5
|
+
const styles = {
|
|
6
|
+
sm: {
|
|
7
|
+
root: "gap-4",
|
|
8
|
+
label: "text-sm font-medium",
|
|
9
|
+
spinner: "size-8",
|
|
10
|
+
},
|
|
11
|
+
md: {
|
|
12
|
+
root: "gap-4",
|
|
13
|
+
label: "text-sm font-medium",
|
|
14
|
+
spinner: "size-12",
|
|
15
|
+
},
|
|
16
|
+
lg: {
|
|
17
|
+
root: "gap-4",
|
|
18
|
+
label: "text-lg font-medium",
|
|
19
|
+
spinner: "size-14",
|
|
20
|
+
},
|
|
21
|
+
xl: {
|
|
22
|
+
root: "gap-5",
|
|
23
|
+
label: "text-lg font-medium",
|
|
24
|
+
spinner: "size-16",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface LoadingIndicatorProps {
|
|
29
|
+
/**
|
|
30
|
+
* The visual style of the loading indicator.
|
|
31
|
+
* @default 'line-simple'
|
|
32
|
+
*/
|
|
33
|
+
type?: "line-simple" | "line-spinner" | "dot-circle";
|
|
34
|
+
/**
|
|
35
|
+
* The size of the loading indicator.
|
|
36
|
+
* @default 'sm'
|
|
37
|
+
*/
|
|
38
|
+
size?: "sm" | "md" | "lg" | "xl";
|
|
39
|
+
/**
|
|
40
|
+
* Optional text label displayed below the indicator.
|
|
41
|
+
*/
|
|
42
|
+
label?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const LoadingIndicator = ({ type = "line-simple", size = "sm", label }: LoadingIndicatorProps) => {
|
|
46
|
+
const renderSpinner = () => {
|
|
47
|
+
if (type === "line-spinner") {
|
|
48
|
+
return (
|
|
49
|
+
<svg className={cx("animate-spin", styles[size].spinner)} viewBox="0 0 32 32" fill="none">
|
|
50
|
+
<circle
|
|
51
|
+
className="stroke-fg-brand-primary"
|
|
52
|
+
cx="16"
|
|
53
|
+
cy="16"
|
|
54
|
+
r="14"
|
|
55
|
+
fill="none"
|
|
56
|
+
strokeWidth="4"
|
|
57
|
+
strokeDashoffset="40"
|
|
58
|
+
strokeDasharray="100"
|
|
59
|
+
strokeLinecap="round"
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (type === "dot-circle") {
|
|
66
|
+
return (
|
|
67
|
+
<svg className={cx("animate-spin text-fg-brand-primary", styles[size].spinner)} viewBox="0 0 36 36" fill="none">
|
|
68
|
+
<path
|
|
69
|
+
d="M34 18C34 15.8989 33.5861 13.8183 32.7821 11.8771C31.978 9.93586 30.7994 8.17203 29.3137 6.68629C27.828 5.20055 26.0641 4.022 24.1229 3.21793C22.1817 2.41385 20.1011 2 18 2C15.8988 2 13.8183 2.41385 11.8771 3.21793C9.93585 4.022 8.17203 5.20055 6.68629 6.68629C5.20055 8.17203 4.022 9.93586 3.21793 11.8771C2.41385 13.8183 2 15.8989 2 18"
|
|
70
|
+
stroke="url(#paint0)"
|
|
71
|
+
strokeWidth="4"
|
|
72
|
+
strokeLinecap="round"
|
|
73
|
+
strokeLinejoin="round"
|
|
74
|
+
strokeDasharray="0.1 8"
|
|
75
|
+
/>
|
|
76
|
+
<path
|
|
77
|
+
d="M3.21793 24.1229C4.022 26.0641 5.20055 27.828 6.68629 29.3137C8.17203 30.7994 9.93585 31.978 11.8771 32.7821C13.8183 33.5861 15.8988 34 18 34C20.1011 34 22.1817 33.5861 24.1229 32.7821C26.0641 31.978 27.828 30.7994 29.3137 29.3137C30.7994 27.828 31.978 26.0641 32.7821 24.1229"
|
|
78
|
+
stroke="url(#paint1)"
|
|
79
|
+
strokeWidth="4"
|
|
80
|
+
strokeLinecap="round"
|
|
81
|
+
strokeLinejoin="round"
|
|
82
|
+
strokeDasharray="0.1 8"
|
|
83
|
+
/>
|
|
84
|
+
<defs>
|
|
85
|
+
<linearGradient id="paint0" x1="34" y1="18" x2="2" y2="18" gradientUnits="userSpaceOnUse">
|
|
86
|
+
<stop stopColor="currentColor" />
|
|
87
|
+
<stop offset="1" stopColor="currentColor" stopOpacity="0.5" />
|
|
88
|
+
</linearGradient>
|
|
89
|
+
<linearGradient id="paint1" x1="33" y1="23.5" x2="3" y2="24" gradientUnits="userSpaceOnUse">
|
|
90
|
+
<stop stopOpacity="0" stopColor="currentColor" />
|
|
91
|
+
<stop offset="1" stopColor="currentColor" stopOpacity="0.48" />
|
|
92
|
+
</linearGradient>
|
|
93
|
+
</defs>
|
|
94
|
+
</svg>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Default case: type === "line-simple"
|
|
99
|
+
return (
|
|
100
|
+
<svg className={cx("animate-spin", styles[size].spinner)} viewBox="0 0 32 32" fill="none">
|
|
101
|
+
<circle className="text-bg-tertiary" cx="16" cy="16" r="14" stroke="currentColor" strokeWidth="4" />
|
|
102
|
+
<circle
|
|
103
|
+
className="stroke-fg-brand-primary"
|
|
104
|
+
cx="16"
|
|
105
|
+
cy="16"
|
|
106
|
+
r="14"
|
|
107
|
+
fill="none"
|
|
108
|
+
strokeWidth="4"
|
|
109
|
+
strokeDashoffset="75"
|
|
110
|
+
strokeDasharray="100"
|
|
111
|
+
strokeLinecap="round"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className={cx("flex flex-col items-center justify-center", styles[size].root)}>
|
|
119
|
+
{renderSpinner()}
|
|
120
|
+
{label && <span className={cx("text-secondary", styles[size].label)}>{label}</span>}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
};
|