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.
Files changed (182) hide show
  1. package/README.md +179 -0
  2. package/package.json +59 -0
  3. package/src/contexts/ThemeContext.tsx +34 -0
  4. package/src/contexts/index.ts +1 -0
  5. package/src/design_system/elements/IconComponent.tsx +98 -0
  6. package/src/design_system/elements/avatar/avatar-label-group.tsx +30 -0
  7. package/src/design_system/elements/avatar/avatar-profile-photo.tsx +125 -0
  8. package/src/design_system/elements/avatar/avatar.tsx +131 -0
  9. package/src/design_system/elements/avatar/base-components/avatar-add-button.tsx +34 -0
  10. package/src/design_system/elements/avatar/base-components/avatar-company-icon.tsx +26 -0
  11. package/src/design_system/elements/avatar/base-components/avatar-online-indicator.tsx +31 -0
  12. package/src/design_system/elements/avatar/base-components/index.tsx +4 -0
  13. package/src/design_system/elements/avatar/base-components/verified-tick.tsx +34 -0
  14. package/src/design_system/elements/avatar/utils.ts +12 -0
  15. package/src/design_system/elements/badges/avatar.tsx +132 -0
  16. package/src/design_system/elements/badges/badge-groups.tsx +176 -0
  17. package/src/design_system/elements/badges/badge-types.ts +266 -0
  18. package/src/design_system/elements/badges/badges.tsx +430 -0
  19. package/src/design_system/elements/breadcrumb/Breadcrumb.tsx +33 -0
  20. package/src/design_system/elements/button-group/button-group.tsx +106 -0
  21. package/src/design_system/elements/buttons/app-store-buttons-outline.tsx +378 -0
  22. package/src/design_system/elements/buttons/app-store-buttons.tsx +567 -0
  23. package/src/design_system/elements/buttons/button-utility.tsx +116 -0
  24. package/src/design_system/elements/buttons/button.aman.tsx +174 -0
  25. package/src/design_system/elements/buttons/button.tsx +271 -0
  26. package/src/design_system/elements/buttons/close-button.tsx +42 -0
  27. package/src/design_system/elements/buttons/round-button.tsx +29 -0
  28. package/src/design_system/elements/buttons/social-button.tsx +148 -0
  29. package/src/design_system/elements/buttons/social-logos.tsx +115 -0
  30. package/src/design_system/elements/carousel/carousel-base.tsx +308 -0
  31. package/src/design_system/elements/carousel/carousel.tsx +308 -0
  32. package/src/design_system/elements/checkbox/checkbox.tsx +120 -0
  33. package/src/design_system/elements/date-picker/calendar.tsx +101 -0
  34. package/src/design_system/elements/date-picker/cell.tsx +106 -0
  35. package/src/design_system/elements/date-picker/date-input.tsx +32 -0
  36. package/src/design_system/elements/date-picker/date-picker.tsx +86 -0
  37. package/src/design_system/elements/date-picker/date-range-picker.tsx +163 -0
  38. package/src/design_system/elements/date-picker/range-calendar.tsx +161 -0
  39. package/src/design_system/elements/date-picker/range-preset.tsx +28 -0
  40. package/src/design_system/elements/featured-icon/featured-icon.tsx +154 -0
  41. package/src/design_system/elements/form/form.tsx +10 -0
  42. package/src/design_system/elements/form/hook-form.tsx +75 -0
  43. package/src/design_system/elements/hint-text/hint-text.tsx +33 -0
  44. package/src/design_system/elements/index.tsx +158 -0
  45. package/src/design_system/elements/input/hint-text.tsx +33 -0
  46. package/src/design_system/elements/input/input-group.tsx +133 -0
  47. package/src/design_system/elements/input/input.aman.tsx +172 -0
  48. package/src/design_system/elements/input/input.tsx +271 -0
  49. package/src/design_system/elements/input/label.tsx +50 -0
  50. package/src/design_system/elements/label/label.tsx +50 -0
  51. package/src/design_system/elements/loading-indicator/loading-indicator.tsx +123 -0
  52. package/src/design_system/elements/map/GoogleMap.tsx +286 -0
  53. package/src/design_system/elements/markdown-renderer/MarkdownRenderer.tsx +155 -0
  54. package/src/design_system/elements/modals/modal.tsx +41 -0
  55. package/src/design_system/elements/pagination/pagination-base.tsx +378 -0
  56. package/src/design_system/elements/pagination/pagination-dot.tsx +54 -0
  57. package/src/design_system/elements/pagination/pagination-line.tsx +50 -0
  58. package/src/design_system/elements/pagination/pagination.tsx +330 -0
  59. package/src/design_system/elements/photo-fallback/photo-fallback.tsx +143 -0
  60. package/src/design_system/elements/progress-indicators/progress-circles.tsx +176 -0
  61. package/src/design_system/elements/progress-indicators/progress-indicators.tsx +123 -0
  62. package/src/design_system/elements/progress-indicators/simple-circle.tsx +29 -0
  63. package/src/design_system/elements/radio-buttons/radio-buttons.tsx +129 -0
  64. package/src/design_system/elements/rating/rating-badge.tsx +144 -0
  65. package/src/design_system/elements/rating/rating-stars.tsx +77 -0
  66. package/src/design_system/elements/select/combobox.tsx +152 -0
  67. package/src/design_system/elements/select/multi-select.tsx +363 -0
  68. package/src/design_system/elements/select/popover.tsx +34 -0
  69. package/src/design_system/elements/select/select-item.tsx +97 -0
  70. package/src/design_system/elements/select/select-native.tsx +69 -0
  71. package/src/design_system/elements/select/select.aman.tsx +75 -0
  72. package/src/design_system/elements/select/select.tsx +146 -0
  73. package/src/design_system/elements/shared-assets/credit-card/credit-card.tsx +237 -0
  74. package/src/design_system/elements/shared-assets/credit-card/icons.tsx +75 -0
  75. package/src/design_system/elements/shared-assets/iphone-mockup.tsx +172 -0
  76. package/src/design_system/elements/shared-assets/section-divider.tsx +12 -0
  77. package/src/design_system/elements/slideout-menus/slideout-menu.tsx +122 -0
  78. package/src/design_system/elements/tabs/tabs.tsx +225 -0
  79. package/src/design_system/elements/tags/base-components/tag-checkbox.tsx +45 -0
  80. package/src/design_system/elements/tags/base-components/tag-close-x.tsx +34 -0
  81. package/src/design_system/elements/tags/tags.tsx +176 -0
  82. package/src/design_system/elements/textarea/textarea.aman.tsx +52 -0
  83. package/src/design_system/elements/textarea/textarea.tsx +111 -0
  84. package/src/design_system/elements/toggle/toggle.tsx +140 -0
  85. package/src/design_system/elements/tooltip/tooltip.tsx +109 -0
  86. package/src/design_system/hooks/use-breakpoint.ts +37 -0
  87. package/src/design_system/hooks/use-resize-observer.ts +68 -0
  88. package/src/design_system/logo/keystone-logo-minimal.tsx +93 -0
  89. package/src/design_system/logo/keystone-logo.tsx +22 -0
  90. package/src/design_system/sections/about-home.aman.tsx +85 -0
  91. package/src/design_system/sections/about-home.tsx +115 -0
  92. package/src/design_system/sections/blog-cards.tsx +848 -0
  93. package/src/design_system/sections/blog-gallery.aman.tsx +77 -0
  94. package/src/design_system/sections/blog-gallery.tsx +204 -0
  95. package/src/design_system/sections/blog-home.aman.tsx +84 -0
  96. package/src/design_system/sections/blog-home.tsx +153 -0
  97. package/src/design_system/sections/blog-post.aman.tsx +74 -0
  98. package/src/design_system/sections/blog-post.tsx +301 -0
  99. package/src/design_system/sections/blog-section.aman.tsx +101 -0
  100. package/src/design_system/sections/blog-section.tsx +179 -0
  101. package/src/design_system/sections/contact-home.tsx +25 -0
  102. package/src/design_system/sections/contact-section.aman.tsx +173 -0
  103. package/src/design_system/sections/contact-section.tsx +143 -0
  104. package/src/design_system/sections/faq-grid.aman.tsx +79 -0
  105. package/src/design_system/sections/faq-grid.tsx +102 -0
  106. package/src/design_system/sections/faq-home.aman.tsx +92 -0
  107. package/src/design_system/sections/faq-home.tsx +134 -0
  108. package/src/design_system/sections/feature-tab.tsx +43 -0
  109. package/src/design_system/sections/feature-text.tsx +284 -0
  110. package/src/design_system/sections/footer-home.aman.tsx +62 -0
  111. package/src/design_system/sections/footer-home.tsx +259 -0
  112. package/src/design_system/sections/generic-header-component.tsx +103 -0
  113. package/src/design_system/sections/header-navigation.aman.tsx +360 -0
  114. package/src/design_system/sections/header-navigation.tsx +334 -0
  115. package/src/design_system/sections/hero-faq.aman.tsx +38 -0
  116. package/src/design_system/sections/hero-faq.tsx +55 -0
  117. package/src/design_system/sections/hero-generic-text.aman.tsx +49 -0
  118. package/src/design_system/sections/hero-generic-text.tsx +51 -0
  119. package/src/design_system/sections/hero-home.aman.tsx +84 -0
  120. package/src/design_system/sections/hero-home.tsx +246 -0
  121. package/src/design_system/sections/hero-location-detail.aman.tsx +33 -0
  122. package/src/design_system/sections/hero-location-detail.tsx +72 -0
  123. package/src/design_system/sections/hero-service-detail.aman.tsx +53 -0
  124. package/src/design_system/sections/hero-service-detail.tsx +51 -0
  125. package/src/design_system/sections/hero-social-media.aman.tsx +42 -0
  126. package/src/design_system/sections/hero-social-media.tsx +35 -0
  127. package/src/design_system/sections/hero-testimonials.aman.tsx +38 -0
  128. package/src/design_system/sections/hero-testimonials.tsx +55 -0
  129. package/src/design_system/sections/home-hero-component.tsx +228 -0
  130. package/src/design_system/sections/index.tsx +131 -0
  131. package/src/design_system/sections/job-gallery.aman.tsx +91 -0
  132. package/src/design_system/sections/job-gallery.tsx +183 -0
  133. package/src/design_system/sections/location-details-section.aman.tsx +179 -0
  134. package/src/design_system/sections/location-details-section.tsx +196 -0
  135. package/src/design_system/sections/location-grid.aman.tsx +76 -0
  136. package/src/design_system/sections/location-grid.tsx +123 -0
  137. package/src/design_system/sections/services-grid.aman.tsx +85 -0
  138. package/src/design_system/sections/services-grid.tsx +104 -0
  139. package/src/design_system/sections/services-home.aman.tsx +78 -0
  140. package/src/design_system/sections/services-home.tsx +131 -0
  141. package/src/design_system/sections/social-media-grid.aman.tsx +132 -0
  142. package/src/design_system/sections/social-media-grid.tsx +189 -0
  143. package/src/design_system/sections/statistics-section.aman.tsx +79 -0
  144. package/src/design_system/sections/statistics-section.tsx +97 -0
  145. package/src/design_system/sections/team-grid.aman.tsx +85 -0
  146. package/src/design_system/sections/team-grid.tsx +88 -0
  147. package/src/design_system/sections/testimonials-home.aman.tsx +113 -0
  148. package/src/design_system/sections/testimonials-home.tsx +90 -0
  149. package/src/design_system/sections/values-section.aman.tsx +73 -0
  150. package/src/design_system/sections/values-section.tsx +128 -0
  151. package/src/design_system/utils/icon-mapping.tsx +28 -0
  152. package/src/index.ts +7 -0
  153. package/src/lib/component-registry.ts +53 -0
  154. package/src/lib/hooks/index.ts +8 -0
  155. package/src/lib/hooks/use-breakpoint.ts +37 -0
  156. package/src/lib/hooks/use-clipboard.ts +79 -0
  157. package/src/lib/hooks/use-resize-observer.ts +68 -0
  158. package/src/lib/server-api.ts +115 -0
  159. package/src/styles/style-overrides.aman.css +101 -0
  160. package/src/styles/theme.css +224 -0
  161. package/src/styles/typography.css +430 -0
  162. package/src/themes/index.ts +23 -0
  163. package/src/types/api/blog-post.ts +53 -0
  164. package/src/types/api/company-information.ts +44 -0
  165. package/src/types/api/contact.ts +63 -0
  166. package/src/types/api/faq.ts +37 -0
  167. package/src/types/api/job-posting.ts +34 -0
  168. package/src/types/api/location.ts +36 -0
  169. package/src/types/api/photos.ts +28 -0
  170. package/src/types/api/service.ts +37 -0
  171. package/src/types/api/social-post.ts +28 -0
  172. package/src/types/api/team-member.ts +29 -0
  173. package/src/types/api/testimonial.ts +29 -0
  174. package/src/types/api/website-photos.ts +22 -0
  175. package/src/types/config.ts +21 -0
  176. package/src/types/index.ts +21 -0
  177. package/src/utils/countries.tsx +1351 -0
  178. package/src/utils/cx.ts +25 -0
  179. package/src/utils/gradient-placeholder.ts +59 -0
  180. package/src/utils/is-react-component.ts +33 -0
  181. package/src/utils/markdown-toc.ts +54 -0
  182. 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);