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,148 @@
1
+ "use client";
2
+
3
+ import type { AnchorHTMLAttributes, ButtonHTMLAttributes, DetailedHTMLProps } from "react";
4
+ import type { ButtonProps as AriaButtonProps } from "react-aria-components";
5
+ import { Button as AriaButton, Link as AriaLink } from "react-aria-components";
6
+ import { cx, sortCx } from '../../../utils/cx';
7
+ import { AppleLogo, DribbleLogo, FacebookLogo, FigmaLogo, FigmaLogoOutlined, GoogleLogo, TwitterLogo } from "./social-logos";
8
+
9
+ export const styles = sortCx({
10
+ common: {
11
+ root: "group relative inline-flex h-max cursor-pointer items-center justify-center font-semibold whitespace-nowrap outline-focus-ring transition duration-100 ease-linear before:absolute focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:stroke-fg-disabled disabled:text-fg-disabled disabled:*:text-fg-disabled",
12
+ icon: "pointer-events-none shrink-0 transition-inherit-all",
13
+ },
14
+
15
+ sizes: {
16
+ sm: {
17
+ root: "gap-2 rounded-lg px-3 py-2 text-sm before:rounded-[7px] data-icon-only:p-2",
18
+ },
19
+ md: {
20
+ root: "gap-2.5 rounded-lg px-3.5 py-2.5 text-sm before:rounded-[7px] data-icon-only:p-2.5",
21
+ },
22
+ lg: {
23
+ root: "gap-3 rounded-lg px-4 py-2.5 text-md before:rounded-[7px] data-icon-only:p-2.5",
24
+ },
25
+ xl: {
26
+ root: "gap-3.5 rounded-lg px-4.5 py-3 text-md before:rounded-[7px] data-icon-only:p-3.5",
27
+ },
28
+ "2xl": {
29
+ root: "gap-4 rounded-[10px] px-5.5 py-4 text-lg before:rounded-[9px] data-icon-only:p-4",
30
+ },
31
+ },
32
+
33
+ colors: {
34
+ gray: {
35
+ root: "bg-primary text-secondary shadow-xs-skeumorphic ring-1 ring-primary ring-inset hover:bg-primary_hover hover:text-secondary_hover",
36
+ icon: "text-fg-quaternary group-hover:text-fg-quaternary_hover",
37
+ },
38
+ black: {
39
+ root: "bg-black text-white shadow-xs-skeumorphic ring-1 ring-transparent ring-inset before:absolute before:inset-px before:border before:border-white/12 before:mask-b-from-0%",
40
+ icon: "",
41
+ },
42
+
43
+ facebook: {
44
+ root: "bg-[#1877F2] text-white shadow-xs-skeumorphic ring-1 ring-transparent ring-inset before:absolute before:inset-px before:border before:border-white/12 before:mask-b-from-0% hover:bg-[#0C63D4]",
45
+ icon: "",
46
+ },
47
+
48
+ dribble: {
49
+ root: "bg-[#EA4C89] text-white shadow-xs-skeumorphic ring-1 ring-transparent ring-inset before:absolute before:inset-px before:border before:border-white/12 before:mask-b-from-0% hover:bg-[#E62872]",
50
+ icon: "",
51
+ },
52
+ },
53
+ });
54
+
55
+ interface CommonProps {
56
+ social: "google" | "facebook" | "apple" | "twitter" | "figma" | "dribble";
57
+ disabled?: boolean;
58
+ theme?: "brand" | "color" | "gray";
59
+ size?: keyof typeof styles.sizes;
60
+ }
61
+
62
+ interface ButtonProps extends CommonProps, DetailedHTMLProps<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "color" | "slot">, HTMLButtonElement> {
63
+ slot?: AriaButtonProps["slot"];
64
+ }
65
+
66
+ interface LinkProps extends CommonProps, DetailedHTMLProps<Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "color">, HTMLAnchorElement> {}
67
+
68
+ export type SocialButtonProps = ButtonProps | LinkProps;
69
+
70
+ export const SocialButton = ({ size = "lg", theme = "brand", social, className, children, disabled, ...otherProps }: SocialButtonProps) => {
71
+ const href = "href" in otherProps ? otherProps.href : undefined;
72
+ const Component = href ? AriaLink : AriaButton;
73
+
74
+ const isIconOnly = !children;
75
+
76
+ const socialToColor = {
77
+ google: "gray",
78
+ facebook: "facebook",
79
+ apple: "black",
80
+ twitter: "black",
81
+ figma: "black",
82
+ dribble: "dribble",
83
+ } as const;
84
+
85
+ const colorStyles = theme === "brand" ? styles.colors[socialToColor[social]] : styles.colors.gray;
86
+
87
+ const logos = {
88
+ google: GoogleLogo,
89
+ facebook: FacebookLogo,
90
+ apple: AppleLogo,
91
+ twitter: TwitterLogo,
92
+ figma: theme === "gray" ? FigmaLogoOutlined : FigmaLogo,
93
+ dribble: DribbleLogo,
94
+ };
95
+
96
+ const Logo = logos[social];
97
+
98
+ let props = {};
99
+
100
+ if (href) {
101
+ props = {
102
+ ...otherProps,
103
+
104
+ href: disabled ? undefined : href,
105
+
106
+ // Since anchor elements do not support the `disabled` attribute and state,
107
+ // we need to specify `data-rac` and `data-disabled` in order to be able
108
+ // to use the `disabled:` selector in classes.
109
+ ...(disabled ? { "data-rac": true, "data-disabled": true } : {}),
110
+ };
111
+ } else {
112
+ props = {
113
+ ...otherProps,
114
+
115
+ type: otherProps.type || "button",
116
+ isDisabled: disabled,
117
+ };
118
+ }
119
+
120
+ return (
121
+ <Component
122
+ isDisabled={disabled}
123
+ {...props}
124
+ data-icon-only={isIconOnly ? true : undefined}
125
+ className={cx(styles.common.root, styles.sizes[size].root, colorStyles.root, className)}
126
+ >
127
+ <Logo
128
+ className={cx(
129
+ styles.common.icon,
130
+ theme === "gray"
131
+ ? colorStyles.icon
132
+ : theme === "brand" && (social === "facebook" || social === "apple" || social === "twitter")
133
+ ? "text-white"
134
+ : theme === "color" && (social === "apple" || social === "twitter")
135
+ ? "text-alpha-black"
136
+ : "",
137
+ )}
138
+ colorful={
139
+ (theme === "brand" && (social === "google" || social === "figma")) ||
140
+ (theme === "color" && (social === "google" || social === "facebook" || social === "figma" || social === "dribble")) ||
141
+ undefined
142
+ }
143
+ />
144
+
145
+ {children}
146
+ </Component>
147
+ );
148
+ };
@@ -0,0 +1,115 @@
1
+ "use client";
2
+
3
+ import type { SVGProps } from "react";
4
+
5
+ export const GoogleLogo = ({ colorful, ...props }: SVGProps<SVGSVGElement> & { colorful?: boolean }) => {
6
+ return (
7
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" {...props}>
8
+ <path
9
+ d="M23.766 12.2764C23.766 11.4607 23.6999 10.6406 23.5588 9.83807H12.24V14.4591H18.7217C18.4528 15.9494 17.5885 17.2678 16.323 18.1056V21.1039H20.19C22.4608 19.0139 23.766 15.9274 23.766 12.2764Z"
10
+ fill={colorful ? "#4285F4" : "currentColor"}
11
+ />
12
+ <path
13
+ d="M12.24 24.0008C15.4764 24.0008 18.2058 22.9382 20.1944 21.1039L16.3274 18.1055C15.2516 18.8375 13.8626 19.252 12.2444 19.252C9.11376 19.252 6.45934 17.1399 5.50693 14.3003H1.51648V17.3912C3.55359 21.4434 7.70278 24.0008 12.24 24.0008Z"
14
+ fill={colorful ? "#34A853" : "currentColor"}
15
+ />
16
+ <path
17
+ d="M5.50253 14.3003C4.99987 12.8099 4.99987 11.1961 5.50253 9.70575V6.61481H1.51649C-0.18551 10.0056 -0.18551 14.0004 1.51649 17.3912L5.50253 14.3003Z"
18
+ fill={colorful ? "#FBBC04" : "currentColor"}
19
+ />
20
+ <path
21
+ d="M12.24 4.74966C13.9508 4.7232 15.6043 5.36697 16.8433 6.54867L20.2694 3.12262C18.1 1.0855 15.2207 -0.034466 12.24 0.000808666C7.70277 0.000808666 3.55359 2.55822 1.51648 6.61481L5.50252 9.70575C6.45052 6.86173 9.10935 4.74966 12.24 4.74966Z"
22
+ fill={colorful ? "#EA4335" : "currentColor"}
23
+ />
24
+ </svg>
25
+ );
26
+ };
27
+
28
+ export const FigmaLogo = ({ colorful, ...props }: SVGProps<SVGSVGElement> & { colorful?: boolean }) => {
29
+ return (
30
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" {...props}>
31
+ <path
32
+ d="M8.00006 24.0001C10.2081 24.0001 12.0001 22.208 12.0001 20V16H8.00006C5.79205 16 4 17.792 4 20C4 22.208 5.79205 24.0001 8.00006 24.0001Z"
33
+ fill={colorful ? "#24CB71" : "currentColor"}
34
+ />
35
+ <path d="M4 12C4 9.79203 5.79205 8 8.00006 8H12.0001V16H8.00006C5.79205 16.0001 4 14.208 4 12Z" fill={colorful ? "#874FFF" : "currentColor"} />
36
+ <path
37
+ d="M4 4.00003C4 1.79203 5.79205 0 8.00006 0H12.0001V7.99997H8.00006C5.79205 7.99997 4 6.20803 4 4.00003Z"
38
+ fill={colorful ? "#FF3737" : "currentColor"}
39
+ />
40
+ <path
41
+ d="M12 0H16.0001C18.2081 0 20.0001 1.79203 20.0001 4.00003C20.0001 6.20803 18.2081 7.99997 16.0001 7.99997H12V0Z"
42
+ fill={colorful ? "#FF7237" : "currentColor"}
43
+ />
44
+ <path
45
+ d="M20.0001 12C20.0001 14.208 18.2081 16.0001 16.0001 16.0001C13.792 16.0001 12 14.208 12 12C12 9.79203 13.792 8 16.0001 8C18.2081 8 20.0001 9.79203 20.0001 12Z"
46
+ fill={colorful ? "#00B6FF" : "currentColor"}
47
+ />
48
+ </svg>
49
+ );
50
+ };
51
+
52
+ export const FigmaLogoOutlined = (props: SVGProps<SVGSVGElement>) => {
53
+ return (
54
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" {...props}>
55
+ <path
56
+ fillRule="evenodd"
57
+ clipRule="evenodd"
58
+ d="M8.25 2C7.51349 2 6.81155 2.28629 6.29747 2.78895C5.78414 3.29087 5.5 3.96677 5.5 4.66667C5.5 5.36657 5.78414 6.04247 6.29747 6.54438C6.81155 7.04705 7.51349 7.33333 8.25 7.33333H11V2H8.25ZM13 2V7.33333H15.75C16.1142 7.33333 16.4744 7.26316 16.8097 7.12736C17.145 6.99157 17.4482 6.79311 17.7025 6.54438C17.9569 6.29571 18.1574 6.00171 18.2938 5.67977C18.4301 5.35788 18.5 5.0137 18.5 4.66667C18.5 4.31964 18.4301 3.97545 18.2938 3.65356C18.1574 3.33162 17.9569 3.03763 17.7025 2.78895C17.4482 2.54022 17.145 2.34177 16.8097 2.20598C16.4744 2.07017 16.1142 2 15.75 2H13ZM18.6884 8.33334C18.8324 8.22191 18.9702 8.10211 19.1008 7.9744C19.5429 7.54211 19.8948 7.02769 20.1353 6.45991C20.3759 5.89208 20.5 5.28266 20.5 4.66667C20.5 4.05067 20.3759 3.44126 20.1353 2.87342C19.8948 2.30564 19.5429 1.79122 19.1008 1.35894C18.6587 0.926696 18.1351 0.584984 17.5605 0.352241C16.9858 0.119512 16.3707 0 15.75 0H8.25C6.99738 0 5.79167 0.486331 4.89923 1.35894C4.00603 2.23228 3.5 3.42165 3.5 4.66667C3.5 5.91169 4.00603 7.10105 4.89923 7.9744C5.03021 8.10247 5.16794 8.22222 5.31158 8.33333C5.16794 8.44445 5.03021 8.5642 4.89923 8.69227C4.00603 9.56562 3.5 10.755 3.5 12C3.5 13.245 4.00603 14.4344 4.89923 15.3077C5.03022 15.4358 5.16795 15.5556 5.31159 15.6667C5.16795 15.7778 5.03022 15.8975 4.89923 16.0256C4.00603 16.899 3.5 18.0883 3.5 19.3333C3.5 20.5784 4.00603 21.7677 4.89923 22.6411C5.79167 23.5137 6.99738 24 8.25 24C9.5026 24 10.7083 23.5137 11.6008 22.6411C12.494 21.7677 13 20.5784 13 19.3333V15.8051C13.2922 16.0089 13.6073 16.1799 13.9395 16.3144C14.5142 16.5472 15.1293 16.6667 15.75 16.6667C16.3707 16.6667 16.9858 16.5472 17.5605 16.3144C18.1351 16.0817 18.6587 15.74 19.1008 15.3077C19.5429 14.8754 19.8948 14.361 20.1353 13.7932C20.3759 13.2254 20.5 12.616 20.5 12C20.5 11.384 20.3759 10.7746 20.1353 10.2068C19.8948 9.63898 19.5429 9.12456 19.1008 8.69227C18.9702 8.56456 18.8324 8.44476 18.6884 8.33334ZM11 14.6667V9.33333H8.25C7.51349 9.33333 6.81155 9.61962 6.29747 10.1223C5.78414 10.6242 5.5 11.3001 5.5 12C5.5 12.6999 5.78414 13.3758 6.29747 13.8777C6.81155 14.3804 7.51349 14.6667 8.25 14.6667H11ZM11 16.6667H8.25C7.51349 16.6667 6.81155 16.953 6.29747 17.4556C5.78414 17.9575 5.5 18.6334 5.5 19.3333C5.5 20.0332 5.78414 20.7091 6.29747 21.2111C6.81155 21.7137 7.51349 22 8.25 22C8.98651 22 9.6884 21.7137 10.2025 21.2111C10.7159 20.7091 11 20.0332 11 19.3333V16.6667ZM15.75 9.33333C15.3858 9.33333 15.0256 9.4035 14.6903 9.53931C14.355 9.6751 14.0518 9.87356 13.7975 10.1223C13.5431 10.371 13.3426 10.665 13.2062 10.9869C13.0699 11.3088 13 11.653 13 12C13 12.347 13.0699 12.6912 13.2062 13.0131C13.3426 13.335 13.5431 13.629 13.7975 13.8777C14.0518 14.1264 14.355 14.3249 14.6903 14.4607C15.0256 14.5965 15.3858 14.6667 15.75 14.6667C16.1142 14.6667 16.4744 14.5965 16.8097 14.4607C17.145 14.3249 17.4482 14.1264 17.7025 13.8777C17.9569 13.629 18.1574 13.335 18.2938 13.0131C18.4301 12.6912 18.5 12.347 18.5 12C18.5 11.653 18.4301 11.3088 18.2938 10.9869C18.1574 10.665 17.9569 10.371 17.7025 10.1223C17.4482 9.87356 17.145 9.6751 16.8097 9.53931C16.4744 9.4035 16.1142 9.33333 15.75 9.33333Z"
59
+ fill="currentColor"
60
+ />
61
+ </svg>
62
+ );
63
+ };
64
+
65
+ export const DribbleLogo = ({ colorful, ...props }: SVGProps<SVGSVGElement> & { colorful?: boolean }) => {
66
+ return (
67
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" {...props}>
68
+ <path
69
+ d="M12 23.625C18.4203 23.625 23.625 18.4203 23.625 12C23.625 5.57969 18.4203 0.375 12 0.375C5.57969 0.375 0.375 5.57969 0.375 12C0.375 18.4203 5.57969 23.625 12 23.625Z"
70
+ fill={colorful ? "#EA4C89" : "none"}
71
+ />
72
+ <path
73
+ fillRule="evenodd"
74
+ clipRule="evenodd"
75
+ d="M12 0C5.37527 0 0 5.37527 0 12C0 18.6248 5.37527 24 12 24C18.6117 24 24 18.6248 24 12C24 5.37527 18.6117 0 12 0ZM19.9262 5.53145C21.3579 7.27549 22.217 9.50107 22.243 11.9089C21.9046 11.8439 18.5206 11.154 15.1106 11.5835C15.0325 11.4143 14.9675 11.2321 14.8894 11.0499C14.6811 10.5554 14.4469 10.0477 14.2126 9.56618C17.9869 8.0304 19.705 5.81779 19.9262 5.53145ZM12 1.77007C14.603 1.77007 16.9848 2.74621 18.7939 4.34707C18.6117 4.60738 17.0629 6.67679 13.4186 8.04338C11.7397 4.95878 9.87855 2.43384 9.5922 2.04338C10.3601 1.86117 11.1671 1.77007 12 1.77007ZM7.63995 2.73319C7.91325 3.09761 9.73538 5.63558 11.4404 8.65508C6.65076 9.9306 2.42083 9.90458 1.96529 9.90458C2.62907 6.72885 4.77657 4.08676 7.63995 2.73319ZM1.74404 12.0131C1.74404 11.9089 1.74404 11.8048 1.74404 11.7007C2.18655 11.7136 7.15835 11.7787 12.2733 10.243C12.5727 10.8156 12.846 11.4013 13.1063 11.9869C12.9761 12.026 12.8329 12.0651 12.7028 12.1041C7.41865 13.8091 4.60738 18.4685 4.3731 18.859C2.7462 17.0499 1.74404 14.6421 1.74404 12.0131ZM12 22.256C9.6312 22.256 7.44469 21.449 5.71367 20.0954C5.89588 19.718 7.97827 15.7094 13.757 13.692C13.783 13.679 13.7961 13.679 13.8221 13.666C15.2668 17.4013 15.8525 20.5379 16.0087 21.436C14.7722 21.9696 13.4186 22.256 12 22.256ZM17.7136 20.4989C17.6096 19.8742 17.0629 16.8807 15.7223 13.1974C18.9371 12.6898 21.7484 13.5228 22.0998 13.6399C21.6573 16.4902 20.0173 18.9501 17.7136 20.4989Z"
76
+ fill={colorful ? "#C32361" : "currentColor"}
77
+ />
78
+ </svg>
79
+ );
80
+ };
81
+
82
+ export const FacebookLogo = ({ colorful, ...props }: SVGProps<SVGSVGElement> & { colorful?: boolean }) => {
83
+ return (
84
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" {...props}>
85
+ <path
86
+ d="M24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 17.9895 4.3882 22.954 10.125 23.8542V15.4688H7.07812V12H10.125V9.35625C10.125 6.34875 11.9166 4.6875 14.6576 4.6875C15.9701 4.6875 17.3438 4.92188 17.3438 4.92188V7.875H15.8306C14.34 7.875 13.875 8.80008 13.875 9.75V12H17.2031L16.6711 15.4688H13.875V23.8542C19.6118 22.954 24 17.9895 24 12Z"
87
+ fill={colorful ? "#1877F2" : "currentColor"}
88
+ />
89
+ </svg>
90
+ );
91
+ };
92
+
93
+ export const AppleLogo = (props: SVGProps<SVGSVGElement>) => {
94
+ return (
95
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" {...props}>
96
+ <path
97
+ d="M20.8426 17.1449C20.5099 17.9135 20.1161 18.6211 19.6598 19.2715C19.0379 20.1583 18.5286 20.7721 18.1362 21.113C17.5278 21.6724 16.876 21.959 16.178 21.9753C15.6769 21.9753 15.0726 21.8327 14.3691 21.5434C13.6634 21.2555 13.0148 21.113 12.4218 21.113C11.7998 21.113 11.1328 21.2555 10.4193 21.5434C9.70475 21.8327 9.1291 21.9834 8.68898 21.9984C8.01963 22.0269 7.35246 21.7322 6.6865 21.113C6.26145 20.7422 5.7298 20.1067 5.09291 19.2063C4.40957 18.2449 3.84778 17.13 3.40766 15.8589C2.9363 14.486 2.70001 13.1565 2.70001 11.8694C2.70001 10.3951 3.01859 9.12345 3.65671 8.05784C4.15821 7.20191 4.82539 6.52672 5.66041 6.03105C6.49543 5.53539 7.39768 5.2828 8.36931 5.26664C8.90096 5.26664 9.59815 5.43109 10.4645 5.75429C11.3285 6.07858 11.8832 6.24303 12.1264 6.24303C12.3083 6.24303 12.9245 6.05074 13.9692 5.66738C14.9571 5.31186 15.7909 5.16466 16.474 5.22264C18.3249 5.37202 19.7155 6.10167 20.6403 7.41619C18.9849 8.4192 18.1661 9.82403 18.1824 11.6262C18.1973 13.03 18.7065 14.1981 19.7074 15.1256C20.1609 15.5561 20.6675 15.8888 21.231 16.1251C21.1088 16.4795 20.9798 16.819 20.8426 17.1449ZM16.5976 0.440369C16.5976 1.54062 16.1956 2.56792 15.3944 3.51878C14.4275 4.64917 13.258 5.30236 11.9898 5.19929C11.9737 5.06729 11.9643 4.92837 11.9643 4.78239C11.9643 3.72615 12.4241 2.59576 13.2407 1.67152C13.6483 1.20356 14.1668 0.814453 14.7955 0.504058C15.4229 0.198295 16.0164 0.0292007 16.5745 0.000244141C16.5908 0.147331 16.5976 0.294426 16.5976 0.440355V0.440369Z"
98
+ fill="currentColor"
99
+ />
100
+ </svg>
101
+ );
102
+ };
103
+
104
+ export const TwitterLogo = (props: SVGProps<SVGSVGElement>) => {
105
+ return (
106
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" {...props}>
107
+ <path
108
+ fillRule="evenodd"
109
+ clipRule="evenodd"
110
+ d="M15.9455 23L10.396 15.0901L3.44886 23H0.509766L9.09209 13.2311L0.509766 1H8.05571L13.286 8.45502L19.8393 1H22.7784L14.5943 10.3165L23.4914 23H15.9455ZM19.2185 20.77H17.2398L4.71811 3.23H6.6971L11.7121 10.2532L12.5793 11.4719L19.2185 20.77Z"
111
+ fill="currentColor"
112
+ />
113
+ </svg>
114
+ );
115
+ };
@@ -0,0 +1,308 @@
1
+ "use client";
2
+
3
+ import type { CSSProperties, ComponentPropsWithRef, HTMLAttributes, KeyboardEvent, ReactNode, Ref } from "react";
4
+ import { cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useState } from "react";
5
+ import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
6
+ import { cx } from '../../../utils/cx';
7
+
8
+ type CarouselApi = UseEmblaCarouselType[1];
9
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
10
+ type CarouselOptions = UseCarouselParameters[0];
11
+ type CarouselPlugin = UseCarouselParameters[1];
12
+
13
+ type CarouselProps = {
14
+ /** The options for the Embla carousel. */
15
+ opts?: CarouselOptions;
16
+ /** The plugins for the Embla carousel. */
17
+ plugins?: CarouselPlugin;
18
+ /** The orientation of the carousel. */
19
+ orientation?: "horizontal" | "vertical";
20
+ /** The function to set the API for the carousel. */
21
+ setApi?: (api: CarouselApi) => void;
22
+ };
23
+
24
+ type CarouselContextProps = CarouselProps & {
25
+ /** The ref of the carousel. */
26
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
27
+ /** The API of the carousel. */
28
+ api: ReturnType<typeof useEmblaCarousel>[1];
29
+ /** The function to scroll the carousel to the previous slide. */
30
+ scrollPrev: () => void;
31
+ /** The function to scroll the carousel to the next slide. */
32
+ scrollNext: () => void;
33
+ /** Whether the carousel can scroll to the previous slide. */
34
+ canScrollPrev: boolean;
35
+ /** Whether the carousel can scroll to the next slide. */
36
+ canScrollNext: boolean;
37
+ /** The index of the selected slide. */
38
+ selectedIndex: number;
39
+ /** The scroll snaps of the carousel. */
40
+ scrollSnaps: number[];
41
+ };
42
+
43
+ export const CarouselContext = createContext<CarouselContextProps | null>(null);
44
+
45
+ export const useCarousel = () => {
46
+ const context = useContext(CarouselContext);
47
+
48
+ if (!context) {
49
+ throw new Error("The `useCarousel` hook must be used within a <Carousel />");
50
+ }
51
+
52
+ return context;
53
+ };
54
+
55
+ const CarouselRoot = ({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }: ComponentPropsWithRef<"div"> & CarouselProps) => {
56
+ const [carouselRef, api] = useEmblaCarousel(
57
+ {
58
+ ...opts,
59
+ axis: orientation === "horizontal" ? "x" : "y",
60
+ },
61
+ plugins,
62
+ );
63
+ const [canScrollPrev, setCanScrollPrev] = useState(false);
64
+ const [canScrollNext, setCanScrollNext] = useState(false);
65
+ const [selectedIndex, setSelectedIndex] = useState(0);
66
+ const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);
67
+
68
+ const onInit = useCallback((api: CarouselApi) => {
69
+ if (!api) return;
70
+
71
+ setScrollSnaps(api.scrollSnapList());
72
+ }, []);
73
+
74
+ const onSelect = useCallback((api: CarouselApi) => {
75
+ if (!api) return;
76
+
77
+ setCanScrollPrev(api.canScrollPrev());
78
+ setCanScrollNext(api.canScrollNext());
79
+ setSelectedIndex(api.selectedScrollSnap());
80
+ }, []);
81
+
82
+ const scrollPrev = useCallback(() => {
83
+ api?.scrollPrev();
84
+ }, [api]);
85
+
86
+ const scrollNext = useCallback(() => {
87
+ api?.scrollNext();
88
+ }, [api]);
89
+
90
+ const handleKeyDown = useCallback(
91
+ (event: KeyboardEvent<HTMLDivElement>) => {
92
+ if (event.key === "ArrowLeft") {
93
+ event.preventDefault();
94
+ scrollPrev();
95
+ } else if (event.key === "ArrowRight") {
96
+ event.preventDefault();
97
+ scrollNext();
98
+ }
99
+ },
100
+ [scrollPrev, scrollNext],
101
+ );
102
+
103
+ useEffect(() => {
104
+ if (!api || !setApi) return;
105
+
106
+ setApi(api);
107
+ }, [api, setApi]);
108
+
109
+ useEffect(() => {
110
+ if (!api) return;
111
+
112
+ onInit(api);
113
+ onSelect(api);
114
+
115
+ api.on("reInit", onInit);
116
+ api.on("reInit", onSelect);
117
+ api.on("select", onSelect);
118
+
119
+ return () => {
120
+ api?.off("select", onSelect);
121
+ };
122
+ }, [api, onInit, onSelect]);
123
+
124
+ return (
125
+ <CarouselContext.Provider
126
+ value={{
127
+ carouselRef,
128
+ api: api,
129
+ opts,
130
+ orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
131
+ scrollPrev,
132
+ scrollNext,
133
+ canScrollPrev,
134
+ canScrollNext,
135
+ selectedIndex,
136
+ scrollSnaps,
137
+ }}
138
+ >
139
+ <div onKeyDownCapture={handleKeyDown} className={cx("relative", className)} role="region" aria-roledescription="carousel" {...props}>
140
+ {children}
141
+ </div>
142
+ </CarouselContext.Provider>
143
+ );
144
+ };
145
+
146
+ interface CarouselContentProps extends ComponentPropsWithRef<"div"> {
147
+ /** The class name of the content. */
148
+ className?: string;
149
+ /** Whether to hide the overflow. */
150
+ overflowHidden?: boolean;
151
+ }
152
+
153
+ const CarouselContent = ({ className, overflowHidden = true, ...props }: CarouselContentProps) => {
154
+ const { carouselRef, orientation } = useCarousel();
155
+
156
+ return (
157
+ <div ref={carouselRef} className={cx("h-full w-full", overflowHidden && "overflow-hidden")}>
158
+ <div className={cx("flex max-h-full", orientation === "horizontal" ? "" : "flex-col", className)} {...props} />
159
+ </div>
160
+ );
161
+ };
162
+
163
+ const CarouselItem = ({ className, ...props }: ComponentPropsWithRef<"div">) => {
164
+ return <div role="group" aria-roledescription="slide" className={cx("min-w-0 shrink-0 grow-0 basis-full", className)} {...props} />;
165
+ };
166
+
167
+ interface TriggerRenderProps {
168
+ isDisabled: boolean;
169
+ onClick: () => void;
170
+ }
171
+
172
+ interface TriggerProps {
173
+ /** The ref of the trigger. */
174
+ ref?: Ref<HTMLButtonElement>;
175
+ /** If true, the child element will be cloned and passed down the prop of the trigger. */
176
+ asChild?: boolean;
177
+ /** The direction of the trigger. */
178
+ direction: "prev" | "next";
179
+ /** The children of the trigger. Can be a render prop or a valid element. */
180
+ children: ReactNode | ((props: TriggerRenderProps) => ReactNode);
181
+ /** The style of the trigger. */
182
+ style?: CSSProperties;
183
+ /** The class name of the trigger. */
184
+ className?: string | ((args: { isDisabled: boolean }) => string);
185
+ }
186
+
187
+ const Trigger = ({ className, children, asChild, direction, style, ...props }: TriggerProps) => {
188
+ const { scrollPrev, canScrollNext, scrollNext, canScrollPrev } = useCarousel();
189
+
190
+ const isDisabled = direction === "prev" ? !canScrollPrev : !canScrollNext;
191
+
192
+ const handleClick = () => {
193
+ if (isDisabled) return;
194
+
195
+ direction === "prev" ? scrollPrev() : scrollNext();
196
+ };
197
+
198
+ const computedClassName = typeof className === "function" ? className({ isDisabled }) : className;
199
+
200
+ const defaultAriaLabel = direction === "prev" ? "Previous slide" : "Next slide";
201
+
202
+ // If the children is a render prop, we need to pass the necessary props to the render prop.
203
+ if (typeof children === "function") {
204
+ return <>{children({ isDisabled, onClick: handleClick })}</>;
205
+ }
206
+
207
+ // If the children is a valid element, we need to clone it and pass the necessary props to the cloned element.
208
+ if (asChild && isValidElement(children)) {
209
+ return cloneElement(children, {
210
+ onClick: handleClick,
211
+ disabled: isDisabled,
212
+ "aria-label": defaultAriaLabel,
213
+ style: { ...(children.props as HTMLAttributes<HTMLElement>).style, ...style },
214
+ className: [computedClassName, (children.props as HTMLAttributes<HTMLElement>).className].filter(Boolean).join(" ") || undefined,
215
+ } as HTMLAttributes<HTMLElement>);
216
+ }
217
+
218
+ return (
219
+ <button aria-label={defaultAriaLabel} disabled={isDisabled} className={computedClassName} onClick={handleClick} {...props}>
220
+ {children}
221
+ </button>
222
+ );
223
+ };
224
+
225
+ const CarouselPrevTrigger = (props: Omit<TriggerProps, "direction">) => <Trigger {...props} direction="prev" />;
226
+
227
+ const CarouselNextTrigger = (props: Omit<TriggerProps, "direction">) => <Trigger {...props} direction="next" />;
228
+
229
+ interface CarouselIndicatorRenderProps {
230
+ isSelected: boolean;
231
+ onClick: () => void;
232
+ }
233
+
234
+ interface CarouselIndicatorProps {
235
+ /** The index of the indicator. */
236
+ index: number;
237
+ /** If true, the child element will be cloned and passed down the prop of the indicator. */
238
+ asChild?: boolean;
239
+ /** If true, the indicator will be selected. */
240
+ isSelected?: boolean;
241
+ /** The children of the indicator. Can be a render prop or a valid element. */
242
+ children?: ReactNode | ((props: CarouselIndicatorRenderProps) => ReactNode);
243
+ /** The style of the indicator. */
244
+ style?: CSSProperties;
245
+ /** The class name of the indicator. */
246
+ className?: string | ((args: { isSelected: boolean }) => string);
247
+ }
248
+
249
+ const CarouselIndicator = ({ index, isSelected = false, children, asChild, className, style }: CarouselIndicatorProps) => {
250
+ const { api, selectedIndex } = useCarousel();
251
+
252
+ isSelected = isSelected || selectedIndex === index;
253
+
254
+ const handleClick = () => {
255
+ api?.scrollTo(index);
256
+ };
257
+ const computedClassName = typeof className === "function" ? className({ isSelected }) : className;
258
+
259
+ const defaultAriaLabel = "Go to slide" + (index + 1);
260
+
261
+ // If the children is a render prop, we need to pass the necessary props to the render prop.
262
+ if (typeof children === "function") {
263
+ return <>{children({ isSelected, onClick: handleClick })}</>;
264
+ }
265
+
266
+ // If the children is a valid element, we need to clone it and pass the necessary props to the cloned element.
267
+ if (asChild && isValidElement(children)) {
268
+ return cloneElement(children, {
269
+ onClick: handleClick,
270
+ "aria-label": defaultAriaLabel,
271
+ "aria-current": isSelected ? "true" : undefined,
272
+ style: { ...(children.props as HTMLAttributes<HTMLElement>).style, ...style },
273
+ className: [computedClassName, (children.props as HTMLAttributes<HTMLElement>).className].filter(Boolean).join(" ") || undefined,
274
+ } as HTMLAttributes<HTMLElement>);
275
+ }
276
+
277
+ return (
278
+ <button aria-label={defaultAriaLabel} aria-current={isSelected ? "true" : undefined} className={computedClassName} onClick={handleClick}>
279
+ {children}
280
+ </button>
281
+ );
282
+ };
283
+
284
+ interface CarouselIndicatorGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
285
+ children: ReactNode | ((props: { index: number }) => ReactNode);
286
+ className?: string;
287
+ }
288
+
289
+ const CarouselIndicatorGroup = ({ children, ...props }: CarouselIndicatorGroupProps) => {
290
+ const { scrollSnaps } = useCarousel();
291
+
292
+ // If the children is a render prop, we need to pass the index to the render prop.
293
+ if (typeof children === "function") {
294
+ return <nav {...props}>{scrollSnaps.map((index) => children({ index }))}</nav>;
295
+ }
296
+
297
+ return <nav {...props}>{children}</nav>;
298
+ };
299
+
300
+ export const Carousel = {
301
+ Root: CarouselRoot,
302
+ Content: CarouselContent,
303
+ Item: CarouselItem,
304
+ PrevTrigger: CarouselPrevTrigger,
305
+ NextTrigger: CarouselNextTrigger,
306
+ IndicatorGroup: CarouselIndicatorGroup,
307
+ Indicator: CarouselIndicator,
308
+ };