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,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type ComponentPropsWithRef, type ReactNode, type RefAttributes } from "react";
|
|
4
|
+
import type {
|
|
5
|
+
DialogProps as AriaDialogProps,
|
|
6
|
+
ModalOverlayProps as AriaModalOverlayProps,
|
|
7
|
+
ModalRenderProps as AriaModalRenderProps,
|
|
8
|
+
} from "react-aria-components";
|
|
9
|
+
import { Dialog as AriaDialog, DialogTrigger as AriaDialogTrigger, Modal as AriaModal, ModalOverlay as AriaModalOverlay } from "react-aria-components";
|
|
10
|
+
import { CloseButton } from '../buttons/close-button';
|
|
11
|
+
import { cx } from '../../../utils/cx';
|
|
12
|
+
|
|
13
|
+
interface ModalOverlayProps extends AriaModalOverlayProps, RefAttributes<HTMLDivElement> {}
|
|
14
|
+
|
|
15
|
+
export const ModalOverlay = (props: ModalOverlayProps) => {
|
|
16
|
+
return (
|
|
17
|
+
<AriaModalOverlay
|
|
18
|
+
{...props}
|
|
19
|
+
className={(state) =>
|
|
20
|
+
cx(
|
|
21
|
+
"fixed inset-0 flex min-h-dvh w-full items-center justify-end bg-overlay/70 pl-6 outline-hidden ease-linear md:pl-10",
|
|
22
|
+
state.isEntering && "duration-300 animate-in fade-in",
|
|
23
|
+
state.isExiting && "duration-500 animate-out fade-out",
|
|
24
|
+
typeof props.className === "function" ? props.className(state) : props.className,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
ModalOverlay.displayName = "ModalOverlay";
|
|
31
|
+
|
|
32
|
+
interface ModalProps extends AriaModalOverlayProps, RefAttributes<HTMLDivElement> {}
|
|
33
|
+
|
|
34
|
+
export const Modal = (props: ModalProps) => (
|
|
35
|
+
<AriaModal
|
|
36
|
+
{...props}
|
|
37
|
+
className={(state) =>
|
|
38
|
+
cx(
|
|
39
|
+
"inset-y-0 right-0 h-full w-full max-w-100 shadow-xl transition",
|
|
40
|
+
state.isEntering && "duration-300 animate-in slide-in-from-right",
|
|
41
|
+
state.isExiting && "duration-500 animate-out slide-out-to-right",
|
|
42
|
+
typeof props.className === "function" ? props.className(state) : props.className,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
Modal.displayName = "Modal";
|
|
48
|
+
|
|
49
|
+
interface DialogProps extends AriaDialogProps, RefAttributes<HTMLElement> {}
|
|
50
|
+
|
|
51
|
+
export const Dialog = (props: DialogProps) => (
|
|
52
|
+
<AriaDialog
|
|
53
|
+
role="dialog"
|
|
54
|
+
{...props}
|
|
55
|
+
className={cx(
|
|
56
|
+
"relative flex size-full flex-col items-start gap-6 overflow-y-auto bg-primary ring-1 ring-secondary_alt outline-hidden",
|
|
57
|
+
props.className,
|
|
58
|
+
)}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
Dialog.displayName = "Dialog";
|
|
62
|
+
|
|
63
|
+
interface SlideoutMenuProps extends Omit<AriaModalOverlayProps, "children">, RefAttributes<HTMLDivElement> {
|
|
64
|
+
children: ReactNode | ((children: AriaModalRenderProps & { close: () => void }) => ReactNode);
|
|
65
|
+
dialogClassName?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const Menu = ({ children, dialogClassName, ...props }: SlideoutMenuProps) => {
|
|
69
|
+
return (
|
|
70
|
+
<ModalOverlay {...props}>
|
|
71
|
+
<Modal className={(state) => cx(typeof props.className === "function" ? props.className(state) : props.className)}>
|
|
72
|
+
{(state) => (
|
|
73
|
+
<Dialog className={dialogClassName}>
|
|
74
|
+
{({ close }) => {
|
|
75
|
+
return typeof children === "function" ? children({ ...state, close }) : children;
|
|
76
|
+
}}
|
|
77
|
+
</Dialog>
|
|
78
|
+
)}
|
|
79
|
+
</Modal>
|
|
80
|
+
</ModalOverlay>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
Menu.displayName = "SlideoutMenu";
|
|
84
|
+
|
|
85
|
+
const Content = ({ role = "main", ...props }: ComponentPropsWithRef<"div">) => {
|
|
86
|
+
return <div role={role} {...props} className={cx("flex size-full flex-col gap-6 overflow-y-auto overscroll-auto px-4 md:px-6", props.className)} />;
|
|
87
|
+
};
|
|
88
|
+
Content.displayName = "SlideoutContent";
|
|
89
|
+
|
|
90
|
+
interface SlideoutHeaderProps extends ComponentPropsWithRef<"header"> {
|
|
91
|
+
onClose?: () => void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const Header = ({ className, children, onClose, ...props }: SlideoutHeaderProps) => {
|
|
95
|
+
return (
|
|
96
|
+
<header {...props} className={cx("relative z-1 w-full px-4 pt-6 md:px-6", className)}>
|
|
97
|
+
{children}
|
|
98
|
+
<CloseButton size="md" className="absolute top-3 right-3 shrink-0" onClick={onClose} />
|
|
99
|
+
</header>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
Header.displayName = "SlideoutHeader";
|
|
103
|
+
|
|
104
|
+
const Footer = (props: ComponentPropsWithRef<"footer">) => {
|
|
105
|
+
return <footer {...props} className={cx("w-full p-4 shadow-[inset_0px_1px_0px_0px] shadow-border-secondary md:px-6", props.className)} />;
|
|
106
|
+
};
|
|
107
|
+
Footer.displayName = "SlideoutFooter";
|
|
108
|
+
|
|
109
|
+
const SlideoutMenu = Menu as typeof Menu & {
|
|
110
|
+
Trigger: typeof AriaDialogTrigger;
|
|
111
|
+
Content: typeof Content;
|
|
112
|
+
Header: typeof Header;
|
|
113
|
+
Footer: typeof Footer;
|
|
114
|
+
};
|
|
115
|
+
SlideoutMenu.displayName = "SlideoutMenu";
|
|
116
|
+
|
|
117
|
+
SlideoutMenu.Trigger = AriaDialogTrigger;
|
|
118
|
+
SlideoutMenu.Content = Content;
|
|
119
|
+
SlideoutMenu.Header = Header;
|
|
120
|
+
SlideoutMenu.Footer = Footer;
|
|
121
|
+
|
|
122
|
+
export { SlideoutMenu };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
import { Fragment, createContext, useContext } from "react";
|
|
5
|
+
import type { TabListProps as AriaTabListProps, TabProps as AriaTabProps, TabRenderProps as AriaTabRenderProps } from "react-aria-components";
|
|
6
|
+
import { Tab as AriaTab, TabList as AriaTabList, TabPanel as AriaTabPanel, Tabs as AriaTabs, TabsContext, useSlottedContext } from "react-aria-components";
|
|
7
|
+
import type { BadgeColors } from '../badges/badge-types';
|
|
8
|
+
import { Badge } from '../badges/badges';
|
|
9
|
+
import { cx } from '../../../utils/cx';
|
|
10
|
+
|
|
11
|
+
type Orientation = "horizontal" | "vertical";
|
|
12
|
+
|
|
13
|
+
// Types for different orientations
|
|
14
|
+
type HorizontalTypes = "button-brand" | "button-gray" | "button-border" | "button-minimal" | "underline";
|
|
15
|
+
type VerticalTypes = "button-brand" | "button-gray" | "button-border" | "button-minimal" | "line";
|
|
16
|
+
type TabTypeColors<T> = T extends "horizontal" ? HorizontalTypes : VerticalTypes;
|
|
17
|
+
|
|
18
|
+
// Styles for different types of tab
|
|
19
|
+
const getTabStyles = ({ isFocusVisible, isSelected, isHovered }: AriaTabRenderProps) => ({
|
|
20
|
+
"button-brand": cx(
|
|
21
|
+
"outline-focus-ring",
|
|
22
|
+
isFocusVisible && "outline-2 -outline-offset-2",
|
|
23
|
+
(isSelected || isHovered) && "bg-brand-primary_alt text-brand-secondary",
|
|
24
|
+
),
|
|
25
|
+
"button-gray": cx(
|
|
26
|
+
"outline-focus-ring",
|
|
27
|
+
isHovered && "bg-primary_hover text-secondary",
|
|
28
|
+
isFocusVisible && "outline-2 -outline-offset-2",
|
|
29
|
+
isSelected && "bg-active text-secondary",
|
|
30
|
+
),
|
|
31
|
+
"button-border": cx(
|
|
32
|
+
"outline-focus-ring",
|
|
33
|
+
(isSelected || isHovered) && "bg-primary_alt text-secondary shadow-sm",
|
|
34
|
+
isFocusVisible && "outline-2 -outline-offset-2",
|
|
35
|
+
),
|
|
36
|
+
"button-minimal": cx(
|
|
37
|
+
"rounded-lg outline-focus-ring",
|
|
38
|
+
isHovered && "text-secondary",
|
|
39
|
+
isFocusVisible && "outline-2 -outline-offset-2",
|
|
40
|
+
isSelected && "bg-primary_alt text-secondary shadow-xs ring-1 ring-primary ring-inset",
|
|
41
|
+
),
|
|
42
|
+
underline: cx(
|
|
43
|
+
"rounded-none border-b-2 border-transparent outline-focus-ring",
|
|
44
|
+
(isSelected || isHovered) && "border-fg-brand-primary_alt text-brand-secondary",
|
|
45
|
+
isFocusVisible && "outline-2 -outline-offset-2",
|
|
46
|
+
),
|
|
47
|
+
line: cx(
|
|
48
|
+
"rounded-none border-l-2 border-transparent outline-focus-ring",
|
|
49
|
+
(isSelected || isHovered) && "border-fg-brand-primary_alt text-brand-secondary",
|
|
50
|
+
isFocusVisible && "outline-2 -outline-offset-2",
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const sizes = {
|
|
55
|
+
sm: {
|
|
56
|
+
"button-brand": "text-sm font-semibold py-2 px-3",
|
|
57
|
+
"button-gray": "text-sm font-semibold py-2 px-3",
|
|
58
|
+
"button-border": "text-sm font-semibold py-2 px-3",
|
|
59
|
+
"button-minimal": "text-sm font-semibold py-2 px-3",
|
|
60
|
+
underline: "text-sm font-semibold px-1 pb-2.5 pt-0",
|
|
61
|
+
line: "text-sm font-semibold pl-2.5 pr-3 py-0.5",
|
|
62
|
+
},
|
|
63
|
+
md: {
|
|
64
|
+
"button-brand": "text-md font-semibold py-2.5 px-3",
|
|
65
|
+
"button-gray": "text-md font-semibold py-2.5 px-3",
|
|
66
|
+
"button-border": "text-md font-semibold py-2.5 px-3",
|
|
67
|
+
"button-minimal": "text-md font-semibold py-2.5 px-3",
|
|
68
|
+
underline: "text-md font-semibold px-1 pb-2.5 pt-0",
|
|
69
|
+
line: "text-md font-semibold pr-3.5 pl-3 py-1",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Styles for different types of horizontal tabs
|
|
74
|
+
const getHorizontalStyles = ({ size, fullWidth }: { size?: "sm" | "md"; fullWidth?: boolean }) => ({
|
|
75
|
+
"button-brand": "gap-1",
|
|
76
|
+
"button-gray": "gap-1",
|
|
77
|
+
"button-border": cx("gap-1 rounded-[10px] bg-secondary_alt p-1 ring-1 ring-secondary ring-inset", size === "md" && "rounded-xl p-1.5"),
|
|
78
|
+
"button-minimal": "gap-0.5 rounded-lg bg-secondary_alt ring-1 ring-inset ring-secondary",
|
|
79
|
+
underline: cx("gap-3", fullWidth && "w-full gap-4"),
|
|
80
|
+
line: "gap-2",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const getColorStyles = ({ isSelected, isHovered }: Partial<AriaTabRenderProps>) => ({
|
|
84
|
+
"button-brand": isSelected || isHovered ? "brand" : "gray",
|
|
85
|
+
"button-gray": "gray",
|
|
86
|
+
"button-border": "gray",
|
|
87
|
+
"button-minimal": "gray",
|
|
88
|
+
underline: isSelected || isHovered ? "brand" : "gray",
|
|
89
|
+
line: isSelected || isHovered ? "brand" : "gray",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
interface TabListComponentProps<T extends object, K extends Orientation> extends AriaTabListProps<T> {
|
|
93
|
+
/** The size of the tab list. */
|
|
94
|
+
size?: keyof typeof sizes;
|
|
95
|
+
/** The type of the tab list. */
|
|
96
|
+
type?: TabTypeColors<K>;
|
|
97
|
+
/** The orientation of the tab list. */
|
|
98
|
+
orientation?: K;
|
|
99
|
+
/** The items of the tab list. */
|
|
100
|
+
items: T[];
|
|
101
|
+
/** Whether the tab list is full width. */
|
|
102
|
+
fullWidth?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const TabListContext = createContext<Omit<TabListComponentProps<TabComponentProps, Orientation>, "items">>({
|
|
106
|
+
size: "sm",
|
|
107
|
+
type: "button-brand",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const TabList = <T extends Orientation>({
|
|
111
|
+
size = "sm",
|
|
112
|
+
type = "button-brand",
|
|
113
|
+
orientation: orientationProp,
|
|
114
|
+
fullWidth,
|
|
115
|
+
className,
|
|
116
|
+
children,
|
|
117
|
+
...otherProps
|
|
118
|
+
}: TabListComponentProps<TabComponentProps, T>) => {
|
|
119
|
+
const context = useSlottedContext(TabsContext);
|
|
120
|
+
|
|
121
|
+
const orientation = orientationProp ?? context?.orientation ?? "horizontal";
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<TabListContext.Provider value={{ size, type, orientation, fullWidth }}>
|
|
125
|
+
<AriaTabList
|
|
126
|
+
{...otherProps}
|
|
127
|
+
className={(state) =>
|
|
128
|
+
cx(
|
|
129
|
+
"group flex",
|
|
130
|
+
|
|
131
|
+
getHorizontalStyles({
|
|
132
|
+
size,
|
|
133
|
+
fullWidth,
|
|
134
|
+
})[type as HorizontalTypes],
|
|
135
|
+
|
|
136
|
+
orientation === "vertical" && "w-max flex-col",
|
|
137
|
+
|
|
138
|
+
// Only horizontal tabs with underline type have bottom border
|
|
139
|
+
orientation === "horizontal" &&
|
|
140
|
+
type === "underline" &&
|
|
141
|
+
"relative before:absolute before:inset-x-0 before:bottom-0 before:h-px before:bg-border-secondary",
|
|
142
|
+
|
|
143
|
+
typeof className === "function" ? className(state) : className,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
>
|
|
147
|
+
{children ?? ((item) => <Tab {...item}>{item.children}</Tab>)}
|
|
148
|
+
</AriaTabList>
|
|
149
|
+
</TabListContext.Provider>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const TabPanel = (props: ComponentPropsWithRef<typeof AriaTabPanel>) => {
|
|
154
|
+
return (
|
|
155
|
+
<AriaTabPanel
|
|
156
|
+
{...props}
|
|
157
|
+
className={(state) =>
|
|
158
|
+
cx(
|
|
159
|
+
"outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
160
|
+
typeof props.className === "function" ? props.className(state) : props.className,
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
interface TabComponentProps extends AriaTabProps {
|
|
168
|
+
/** The label of the tab. */
|
|
169
|
+
label?: ReactNode;
|
|
170
|
+
/** The children of the tab. */
|
|
171
|
+
children?: ReactNode | ((props: AriaTabRenderProps) => ReactNode);
|
|
172
|
+
/** The badge displayed next to the label. */
|
|
173
|
+
badge?: number | string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const Tab = (props: TabComponentProps) => {
|
|
177
|
+
const { label, children, badge, ...otherProps } = props;
|
|
178
|
+
const { size = "sm", type = "button-brand", fullWidth } = useContext(TabListContext);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<AriaTab
|
|
182
|
+
{...otherProps}
|
|
183
|
+
className={(prop) =>
|
|
184
|
+
cx(
|
|
185
|
+
"z-10 flex h-max cursor-pointer items-center justify-center gap-2 rounded-md whitespace-nowrap text-quaternary transition duration-100 ease-linear",
|
|
186
|
+
"group-orientation-vertical:justify-start",
|
|
187
|
+
fullWidth && "w-full flex-1",
|
|
188
|
+
sizes[size][type],
|
|
189
|
+
getTabStyles(prop)[type],
|
|
190
|
+
typeof props.className === "function" ? props.className(prop) : props.className,
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
{(state) => (
|
|
195
|
+
<Fragment>
|
|
196
|
+
{typeof children === "function" ? children(state) : children || label}
|
|
197
|
+
{badge && (
|
|
198
|
+
<Badge
|
|
199
|
+
size={size}
|
|
200
|
+
type="pill-color"
|
|
201
|
+
color={getColorStyles(state)[type] as BadgeColors}
|
|
202
|
+
className={cx("hidden transition-inherit-all md:flex", size === "sm" && "-my-px")}
|
|
203
|
+
>
|
|
204
|
+
{badge}
|
|
205
|
+
</Badge>
|
|
206
|
+
)}
|
|
207
|
+
</Fragment>
|
|
208
|
+
)}
|
|
209
|
+
</AriaTab>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const Tabs = ({ className, ...props }: ComponentPropsWithRef<typeof AriaTabs>) => {
|
|
214
|
+
return (
|
|
215
|
+
<AriaTabs
|
|
216
|
+
keyboardActivation="manual"
|
|
217
|
+
{...props}
|
|
218
|
+
className={(state) => cx("flex w-full flex-col", typeof className === "function" ? className(state) : className)}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
Tabs.Panel = TabPanel;
|
|
224
|
+
Tabs.List = TabList;
|
|
225
|
+
Tabs.Item = Tab;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cx } from '../../../../utils/cx';
|
|
4
|
+
|
|
5
|
+
interface TagCheckboxProps {
|
|
6
|
+
size?: "sm" | "md" | "lg";
|
|
7
|
+
className?: string;
|
|
8
|
+
isFocused?: boolean;
|
|
9
|
+
isSelected?: boolean;
|
|
10
|
+
isDisabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const TagCheckbox = ({ className, isFocused, isSelected, isDisabled, size = "sm" }: TagCheckboxProps) => {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className={cx(
|
|
17
|
+
"flex cursor-pointer appearance-none items-center justify-center rounded bg-primary ring-1 ring-primary ring-inset",
|
|
18
|
+
size === "sm" && "size-3.5",
|
|
19
|
+
size === "md" && "size-4",
|
|
20
|
+
size === "lg" && "size-4.5",
|
|
21
|
+
isSelected && "bg-brand-solid ring-bg-brand-solid",
|
|
22
|
+
isDisabled && "cursor-not-allowed bg-disabled_subtle ring-disabled",
|
|
23
|
+
isFocused && "outline-2 outline-offset-2 outline-focus-ring",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
<svg
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
viewBox="0 0 14 14"
|
|
30
|
+
fill="none"
|
|
31
|
+
className={cx(
|
|
32
|
+
"pointer-events-none absolute text-fg-white opacity-0 transition-inherit-all",
|
|
33
|
+
size === "sm" && "size-2.5",
|
|
34
|
+
size === "md" && "size-3",
|
|
35
|
+
size === "lg" && "size-3.5",
|
|
36
|
+
isSelected && "opacity-100",
|
|
37
|
+
isDisabled && "text-fg-disabled_subtle",
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
<path d="M11.6666 3.5L5.24992 9.91667L2.33325 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
41
|
+
</svg>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
TagCheckbox.displayName = "TagCheckbox";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { RefAttributes } from "react";
|
|
4
|
+
import { XClose } from "@untitledui/icons";
|
|
5
|
+
import { Button as AriaButton, type ButtonProps as AriaButtonProps } from "react-aria-components";
|
|
6
|
+
import { cx } from '../../../../utils/cx';
|
|
7
|
+
|
|
8
|
+
interface TagCloseXProps extends AriaButtonProps, RefAttributes<HTMLButtonElement> {
|
|
9
|
+
size?: "sm" | "md" | "lg";
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const styles = {
|
|
14
|
+
sm: { root: "p-0.5", icon: "size-2.5" },
|
|
15
|
+
md: { root: "p-0.5", icon: "size-3" },
|
|
16
|
+
lg: { root: "p-0.75", icon: "size-3.5" },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const TagCloseX = ({ size = "md", className, ...otherProps }: TagCloseXProps) => {
|
|
20
|
+
return (
|
|
21
|
+
<AriaButton
|
|
22
|
+
slot="remove"
|
|
23
|
+
aria-label="Remove this tag"
|
|
24
|
+
className={cx(
|
|
25
|
+
"flex cursor-pointer rounded-[3px] text-fg-quaternary outline-transparent transition duration-100 ease-linear hover:bg-primary_hover hover:text-fg-quaternary_hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring disabled:cursor-not-allowed",
|
|
26
|
+
styles[size].root,
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...otherProps}
|
|
30
|
+
>
|
|
31
|
+
<XClose className={cx("transition-inherit-all", styles[size].icon)} strokeWidth="3" />
|
|
32
|
+
</AriaButton>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type PropsWithChildren, type RefAttributes, createContext, useContext, type HTMLAttributes } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Tag as AriaTag,
|
|
6
|
+
TagGroup as AriaTagGroup,
|
|
7
|
+
type TagGroupProps as AriaTagGroupProps,
|
|
8
|
+
TagList as AriaTagList,
|
|
9
|
+
type TagProps as AriaTagProps,
|
|
10
|
+
} from "react-aria-components";
|
|
11
|
+
import { Avatar } from '../avatar/avatar';
|
|
12
|
+
import { cx } from '../../../utils/cx';
|
|
13
|
+
import { TagCheckbox } from "./base-components/tag-checkbox";
|
|
14
|
+
import { TagCloseX } from "./base-components/tag-close-x";
|
|
15
|
+
|
|
16
|
+
// Inline Dot component (previously from foundations)
|
|
17
|
+
const dotSizes = {
|
|
18
|
+
sm: { wh: 8, c: 4, r: 2.5 },
|
|
19
|
+
md: { wh: 10, c: 5, r: 4 },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const Dot = ({ size = "md", ...props }: HTMLAttributes<HTMLOrSVGElement> & { size?: "sm" | "md" }) => {
|
|
23
|
+
return (
|
|
24
|
+
<svg width={dotSizes[size].wh} height={dotSizes[size].wh} viewBox={`0 0 ${dotSizes[size].wh} ${dotSizes[size].wh}`} fill="none" {...props}>
|
|
25
|
+
<circle cx={dotSizes[size].c} cy={dotSizes[size].c} r={dotSizes[size].r} fill="currentColor" stroke="currentColor" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export interface TagItem {
|
|
31
|
+
id: string;
|
|
32
|
+
label: string;
|
|
33
|
+
count?: number;
|
|
34
|
+
avatarSrc?: string;
|
|
35
|
+
avatarContrastBorder?: boolean;
|
|
36
|
+
dot?: boolean;
|
|
37
|
+
dotClassName?: string;
|
|
38
|
+
isDisabled?: boolean;
|
|
39
|
+
onClose?: (id: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const TagGroupContext = createContext<{
|
|
43
|
+
selectionMode: "none" | "single" | "multiple";
|
|
44
|
+
size: "sm" | "md" | "lg";
|
|
45
|
+
}>({
|
|
46
|
+
selectionMode: "none",
|
|
47
|
+
size: "sm",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
interface TagGroupProps extends AriaTagGroupProps, RefAttributes<HTMLDivElement> {
|
|
51
|
+
label: string;
|
|
52
|
+
size?: "sm" | "md" | "lg";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const TagGroup = ({ label, selectionMode = "none", size = "sm", children, ...otherProps }: TagGroupProps) => {
|
|
56
|
+
return (
|
|
57
|
+
<TagGroupContext.Provider value={{ selectionMode, size }}>
|
|
58
|
+
<AriaTagGroup aria-label={label} selectionMode={selectionMode} disallowEmptySelection={selectionMode === "single"} {...otherProps}>
|
|
59
|
+
{children}
|
|
60
|
+
</AriaTagGroup>
|
|
61
|
+
</TagGroupContext.Provider>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const TagList = AriaTagList;
|
|
66
|
+
|
|
67
|
+
const styles = {
|
|
68
|
+
sm: {
|
|
69
|
+
root: {
|
|
70
|
+
base: "px-2 py-0.75 text-xs font-medium",
|
|
71
|
+
withCheckbox: "pl-1.25",
|
|
72
|
+
withAvatar: "pl-1",
|
|
73
|
+
withDot: "pl-1.5",
|
|
74
|
+
withCount: "pr-1",
|
|
75
|
+
withClose: "pr-1",
|
|
76
|
+
},
|
|
77
|
+
content: "gap-1",
|
|
78
|
+
count: "px-1 text-xs font-medium",
|
|
79
|
+
},
|
|
80
|
+
md: {
|
|
81
|
+
root: {
|
|
82
|
+
base: "px-2.25 py-0.5 text-sm font-medium",
|
|
83
|
+
withCheckbox: "pl-1",
|
|
84
|
+
withAvatar: "pl-1.25",
|
|
85
|
+
withDot: "pl-1.75",
|
|
86
|
+
withCount: "pr-0.75",
|
|
87
|
+
withClose: "pr-1",
|
|
88
|
+
},
|
|
89
|
+
content: "gap-1.25",
|
|
90
|
+
count: "px-1.25 text-xs font-medium",
|
|
91
|
+
},
|
|
92
|
+
lg: {
|
|
93
|
+
root: {
|
|
94
|
+
base: "px-2.5 py-1 text-sm font-medium",
|
|
95
|
+
withCheckbox: "pl-1.25",
|
|
96
|
+
withAvatar: "pl-1.75",
|
|
97
|
+
withDot: "pl-2.25",
|
|
98
|
+
withCount: "pr-1",
|
|
99
|
+
withClose: "pr-1",
|
|
100
|
+
},
|
|
101
|
+
content: "gap-1.5",
|
|
102
|
+
count: "px-1.5 text-sm font-medium",
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
interface TagProps extends AriaTagProps, RefAttributes<object>, Omit<TagItem, "label" | "id"> {}
|
|
107
|
+
|
|
108
|
+
export const Tag = ({
|
|
109
|
+
id,
|
|
110
|
+
avatarSrc,
|
|
111
|
+
avatarContrastBorder,
|
|
112
|
+
dot,
|
|
113
|
+
dotClassName,
|
|
114
|
+
isDisabled,
|
|
115
|
+
count,
|
|
116
|
+
className,
|
|
117
|
+
children,
|
|
118
|
+
onClose,
|
|
119
|
+
}: PropsWithChildren<TagProps>) => {
|
|
120
|
+
const context = useContext(TagGroupContext);
|
|
121
|
+
|
|
122
|
+
const leadingContent = avatarSrc ? (
|
|
123
|
+
<Avatar size="xxs" src={avatarSrc} alt="Avatar" contrastBorder={avatarContrastBorder} />
|
|
124
|
+
) : dot ? (
|
|
125
|
+
<Dot className={cx("text-fg-success-secondary", dotClassName)} size="sm" />
|
|
126
|
+
) : null;
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<AriaTag
|
|
130
|
+
id={id}
|
|
131
|
+
isDisabled={isDisabled}
|
|
132
|
+
textValue={typeof children === "string" ? children : undefined}
|
|
133
|
+
className={(state) =>
|
|
134
|
+
cx(
|
|
135
|
+
"flex cursor-default items-center gap-0.75 rounded-md bg-primary text-secondary ring-1 ring-primary ring-inset focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring",
|
|
136
|
+
styles[context.size].root.base,
|
|
137
|
+
|
|
138
|
+
// With avatar
|
|
139
|
+
avatarSrc && styles[context.size].root.withAvatar,
|
|
140
|
+
// With X button
|
|
141
|
+
(onClose || state.allowsRemoving) && styles[context.size].root.withClose,
|
|
142
|
+
// With dot
|
|
143
|
+
dot && styles[context.size].root.withDot,
|
|
144
|
+
// With count
|
|
145
|
+
typeof count === "number" && styles[context.size].root.withCount,
|
|
146
|
+
// With checkbox
|
|
147
|
+
context.selectionMode !== "none" && styles[context.size].root.withCheckbox,
|
|
148
|
+
// Disabled
|
|
149
|
+
isDisabled && "cursor-not-allowed",
|
|
150
|
+
|
|
151
|
+
typeof className === "function" ? className(state) : className,
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
>
|
|
155
|
+
{({ isSelected, isDisabled, allowsRemoving }) => (
|
|
156
|
+
<>
|
|
157
|
+
<div className={cx("flex items-center gap-1", styles[context.size].content)}>
|
|
158
|
+
{context.selectionMode !== "none" && <TagCheckbox size={context.size} isSelected={isSelected} isDisabled={isDisabled} />}
|
|
159
|
+
|
|
160
|
+
{leadingContent}
|
|
161
|
+
|
|
162
|
+
{children}
|
|
163
|
+
|
|
164
|
+
{typeof count === "number" && (
|
|
165
|
+
<span className={cx("flex items-center justify-center rounded-[3px] bg-tertiary text-center", styles[context.size].count)}>
|
|
166
|
+
{count}
|
|
167
|
+
</span>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{(onClose || allowsRemoving) && <TagCloseX size={context.size} onPress={() => id && onClose?.(id.toString())} />}
|
|
172
|
+
</>
|
|
173
|
+
)}
|
|
174
|
+
</AriaTag>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { TextAreaProps as AriaTextAreaProps, TextFieldProps as AriaTextFieldProps } from "react-aria-components";
|
|
4
|
+
import { TextArea as AriaTextArea, TextField as AriaTextField } from "react-aria-components";
|
|
5
|
+
import { HintText } from '../input/hint-text';
|
|
6
|
+
import { Label } from '../input/label';
|
|
7
|
+
import { cx } from '../../../utils/cx';
|
|
8
|
+
|
|
9
|
+
export interface TextareaProps extends AriaTextFieldProps {
|
|
10
|
+
label?: string;
|
|
11
|
+
hint?: string;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
rows?: number;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TextArea = ({
|
|
18
|
+
label,
|
|
19
|
+
hint,
|
|
20
|
+
placeholder,
|
|
21
|
+
rows = 4,
|
|
22
|
+
isInvalid,
|
|
23
|
+
isDisabled,
|
|
24
|
+
isRequired,
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: TextareaProps) => {
|
|
28
|
+
return (
|
|
29
|
+
<AriaTextField
|
|
30
|
+
{...({ isInvalid, isDisabled, isRequired } as AriaTextFieldProps)}
|
|
31
|
+
className="flex flex-col gap-1.5"
|
|
32
|
+
>
|
|
33
|
+
{label && <Label>{label}</Label>}
|
|
34
|
+
<AriaTextArea
|
|
35
|
+
{...(props as AriaTextAreaProps)}
|
|
36
|
+
rows={rows}
|
|
37
|
+
placeholder={placeholder}
|
|
38
|
+
className={cx(
|
|
39
|
+
"w-full rounded-sm bg-white px-3 py-2.5 text-base font-body text-fg-primary shadow-sm ring-1 ring-secondary ring-inset placeholder:text-secondary outline-none transition-shadow duration-100 ease-linear",
|
|
40
|
+
"focus:ring-2 focus:ring-focus-ring",
|
|
41
|
+
isDisabled && "cursor-not-allowed bg-primary text-secondary ring-secondary",
|
|
42
|
+
isInvalid && "ring-error_subtle focus:ring-2 focus:ring-error",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
/>
|
|
46
|
+
{hint && <HintText>{hint}</HintText>}
|
|
47
|
+
</AriaTextField>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
import { registerThemeVariant } from '../../../lib/component-registry';
|
|
52
|
+
registerThemeVariant('textarea', 'aman', TextArea);
|