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,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, DetailedHTMLProps, FC, ReactNode } from "react";
|
|
4
|
+
import { isValidElement } from "react";
|
|
5
|
+
import type { Placement } from "react-aria";
|
|
6
|
+
import type { ButtonProps as AriaButtonProps } from "react-aria-components";
|
|
7
|
+
import { Button as AriaButton, Link as AriaLink } from "react-aria-components";
|
|
8
|
+
import { Tooltip } from '../tooltip/tooltip';
|
|
9
|
+
import { cx } from '../../../utils/cx';
|
|
10
|
+
import { isReactComponent } from '../../../utils/is-react-component';
|
|
11
|
+
|
|
12
|
+
export const styles = {
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-primary text-fg-quaternary shadow-xs-skeumorphic ring-1 ring-primary ring-inset hover:bg-primary_hover hover:text-fg-quaternary_hover disabled:shadow-xs disabled:ring-disabled_subtle",
|
|
15
|
+
tertiary: "text-fg-quaternary hover:bg-primary_hover hover:text-fg-quaternary_hover",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Common props shared between button and anchor variants
|
|
20
|
+
*/
|
|
21
|
+
export interface CommonProps {
|
|
22
|
+
/** Disables the button and shows a disabled state */
|
|
23
|
+
isDisabled?: boolean;
|
|
24
|
+
/** The size variant of the button */
|
|
25
|
+
size?: "xs" | "sm";
|
|
26
|
+
/** The color variant of the button */
|
|
27
|
+
color?: "secondary" | "tertiary";
|
|
28
|
+
/** The icon to display in the button */
|
|
29
|
+
icon?: FC<{ className?: string }> | ReactNode;
|
|
30
|
+
/** The tooltip to display when hovering over the button */
|
|
31
|
+
tooltip?: string;
|
|
32
|
+
/** The placement of the tooltip */
|
|
33
|
+
tooltipPlacement?: Placement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Props for the button variant (non-link)
|
|
38
|
+
*/
|
|
39
|
+
export interface ButtonProps extends CommonProps, DetailedHTMLProps<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "color" | "slot">, HTMLButtonElement> {
|
|
40
|
+
/** Slot name for react-aria component */
|
|
41
|
+
slot?: AriaButtonProps["slot"];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Props for the link variant (anchor tag)
|
|
46
|
+
*/
|
|
47
|
+
interface LinkProps extends CommonProps, DetailedHTMLProps<Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "color">, HTMLAnchorElement> {}
|
|
48
|
+
|
|
49
|
+
/** Union type of button and link props */
|
|
50
|
+
export type Props = ButtonProps | LinkProps;
|
|
51
|
+
|
|
52
|
+
export const ButtonUtility = ({
|
|
53
|
+
tooltip,
|
|
54
|
+
className,
|
|
55
|
+
isDisabled,
|
|
56
|
+
icon: Icon,
|
|
57
|
+
size = "sm",
|
|
58
|
+
color = "secondary",
|
|
59
|
+
tooltipPlacement = "top",
|
|
60
|
+
...otherProps
|
|
61
|
+
}: Props) => {
|
|
62
|
+
const href = "href" in otherProps ? otherProps.href : undefined;
|
|
63
|
+
const Component = href ? AriaLink : AriaButton;
|
|
64
|
+
|
|
65
|
+
let props = {};
|
|
66
|
+
|
|
67
|
+
if (href) {
|
|
68
|
+
props = {
|
|
69
|
+
...otherProps,
|
|
70
|
+
|
|
71
|
+
href: isDisabled ? undefined : href,
|
|
72
|
+
|
|
73
|
+
// Since anchor elements do not support the `disabled` attribute and state,
|
|
74
|
+
// we need to specify `data-rac` and `data-disabled` in order to be able
|
|
75
|
+
// to use the `disabled:` selector in classes.
|
|
76
|
+
...(isDisabled ? { "data-rac": true, "data-disabled": true } : {}),
|
|
77
|
+
};
|
|
78
|
+
} else {
|
|
79
|
+
props = {
|
|
80
|
+
...otherProps,
|
|
81
|
+
|
|
82
|
+
type: otherProps.type || "button",
|
|
83
|
+
isDisabled,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const content = (
|
|
88
|
+
<Component
|
|
89
|
+
aria-label={tooltip}
|
|
90
|
+
{...props}
|
|
91
|
+
className={cx(
|
|
92
|
+
"group relative inline-flex h-max cursor-pointer items-center justify-center rounded-md p-1.5 outline-focus-ring transition duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:text-fg-disabled_subtle",
|
|
93
|
+
styles[color],
|
|
94
|
+
|
|
95
|
+
// Icon styles
|
|
96
|
+
"*:data-icon:pointer-events-none *:data-icon:shrink-0 *:data-icon:text-current *:data-icon:transition-inherit-all",
|
|
97
|
+
size === "xs" ? "*:data-icon:size-4" : "*:data-icon:size-5",
|
|
98
|
+
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{isReactComponent(Icon) && <Icon data-icon />}
|
|
103
|
+
{isValidElement(Icon) && Icon}
|
|
104
|
+
</Component>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (tooltip) {
|
|
108
|
+
return (
|
|
109
|
+
<Tooltip title={tooltip} placement={tooltipPlacement} isDisabled={isDisabled} offset={size === "xs" ? 4 : 6}>
|
|
110
|
+
{content}
|
|
111
|
+
</Tooltip>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return content;
|
|
116
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, DetailedHTMLProps, FC, ReactNode } from "react";
|
|
4
|
+
import React, { isValidElement } from "react";
|
|
5
|
+
import type { ButtonProps as AriaButtonProps } from "react-aria-components";
|
|
6
|
+
import { Button as AriaButton, Link as AriaLink } from "react-aria-components";
|
|
7
|
+
import { cx, sortCx } from '../../../utils/cx';
|
|
8
|
+
import { isReactComponent } from '../../../utils/is-react-component';
|
|
9
|
+
|
|
10
|
+
// Aman theme: Minimal dark charcoal buttons with white text
|
|
11
|
+
export const styles = sortCx({
|
|
12
|
+
common: {
|
|
13
|
+
root: [
|
|
14
|
+
"group relative inline-flex h-max cursor-pointer items-center justify-center whitespace-nowrap outline-none transition duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring",
|
|
15
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
16
|
+
"*:data-icon:pointer-events-none *:data-icon:size-5 *:data-icon:shrink-0 *:data-icon:transition-inherit-all",
|
|
17
|
+
].join(" "),
|
|
18
|
+
icon: "pointer-events-none size-5 shrink-0 transition-inherit-all",
|
|
19
|
+
},
|
|
20
|
+
sizes: {
|
|
21
|
+
sm: {
|
|
22
|
+
root: "gap-2 rounded-sm px-5 py-2.5 text-sm font-medium uppercase tracking-wide",
|
|
23
|
+
},
|
|
24
|
+
md: {
|
|
25
|
+
root: "gap-2 rounded-sm px-6 py-3 text-base font-medium uppercase tracking-wide",
|
|
26
|
+
},
|
|
27
|
+
lg: {
|
|
28
|
+
root: "gap-2 rounded-sm px-8 py-3.5 text-lg font-medium uppercase tracking-wide",
|
|
29
|
+
},
|
|
30
|
+
xl: {
|
|
31
|
+
root: "gap-2.5 rounded-sm px-10 py-4 text-xl font-medium uppercase tracking-wide",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
colors: {
|
|
36
|
+
primary: {
|
|
37
|
+
root: "bg-primary text-primary_contrast hover:opacity-90",
|
|
38
|
+
},
|
|
39
|
+
secondary: {
|
|
40
|
+
root: "bg-white text-fg-primary border-2 hover:opacity-90 transition-all",
|
|
41
|
+
},
|
|
42
|
+
tertiary: {
|
|
43
|
+
root: "text-fg-primary hover:underline",
|
|
44
|
+
},
|
|
45
|
+
"link-gray": {
|
|
46
|
+
root: [
|
|
47
|
+
"p-0! text-fg-primary",
|
|
48
|
+
"*:data-text:underline *:data-text:underline-offset-4 hover:*:data-text:no-underline",
|
|
49
|
+
].join(" "),
|
|
50
|
+
},
|
|
51
|
+
"link-color": {
|
|
52
|
+
root: [
|
|
53
|
+
"p-0! text-fg-primary",
|
|
54
|
+
"*:data-text:underline *:data-text:underline-offset-4 hover:*:data-text:no-underline",
|
|
55
|
+
].join(" "),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export interface CommonProps {
|
|
61
|
+
isDisabled?: boolean;
|
|
62
|
+
isLoading?: boolean;
|
|
63
|
+
size?: keyof typeof styles.sizes;
|
|
64
|
+
color?: keyof typeof styles.colors;
|
|
65
|
+
iconLeading?: FC<{ className?: string }> | ReactNode;
|
|
66
|
+
iconTrailing?: FC<{ className?: string }> | ReactNode;
|
|
67
|
+
noTextPadding?: boolean;
|
|
68
|
+
showTextWhileLoading?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ButtonProps extends CommonProps, DetailedHTMLProps<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "color" | "slot">, HTMLButtonElement> {
|
|
72
|
+
slot?: AriaButtonProps["slot"];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface LinkProps extends CommonProps, DetailedHTMLProps<Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "color">, HTMLAnchorElement> {}
|
|
76
|
+
|
|
77
|
+
export type Props = ButtonProps | LinkProps;
|
|
78
|
+
|
|
79
|
+
export const Button = ({
|
|
80
|
+
size = "sm",
|
|
81
|
+
color = "primary",
|
|
82
|
+
children,
|
|
83
|
+
className,
|
|
84
|
+
noTextPadding,
|
|
85
|
+
iconLeading: IconLeading,
|
|
86
|
+
iconTrailing: IconTrailing,
|
|
87
|
+
isDisabled: disabled,
|
|
88
|
+
isLoading: loading,
|
|
89
|
+
showTextWhileLoading,
|
|
90
|
+
...otherProps
|
|
91
|
+
}: Props) => {
|
|
92
|
+
const href = "href" in otherProps ? otherProps.href : undefined;
|
|
93
|
+
const Component = href ? AriaLink : AriaButton;
|
|
94
|
+
|
|
95
|
+
const isIcon = (IconLeading || IconTrailing) && !children;
|
|
96
|
+
const isLinkType = ["link-gray", "link-color"].includes(color);
|
|
97
|
+
|
|
98
|
+
noTextPadding = isLinkType || noTextPadding;
|
|
99
|
+
|
|
100
|
+
let props = {};
|
|
101
|
+
|
|
102
|
+
if (href) {
|
|
103
|
+
props = {
|
|
104
|
+
...otherProps,
|
|
105
|
+
href: disabled ? undefined : href,
|
|
106
|
+
...(disabled ? { "data-rac": true, "data-disabled": true } : {}),
|
|
107
|
+
};
|
|
108
|
+
} else {
|
|
109
|
+
props = {
|
|
110
|
+
...otherProps,
|
|
111
|
+
type: otherProps.type || "button",
|
|
112
|
+
isPending: loading,
|
|
113
|
+
isDisabled: disabled,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Dynamic border color for secondary buttons using brand secondary color
|
|
118
|
+
const secondaryStyle = color === 'secondary' ? {
|
|
119
|
+
borderColor: 'var(--color-text-brand-secondary)'
|
|
120
|
+
} : undefined;
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Component
|
|
124
|
+
data-loading={loading ? true : undefined}
|
|
125
|
+
data-icon-only={isIcon ? true : undefined}
|
|
126
|
+
{...props}
|
|
127
|
+
style={secondaryStyle}
|
|
128
|
+
className={cx(
|
|
129
|
+
styles.common.root,
|
|
130
|
+
styles.sizes[size].root,
|
|
131
|
+
styles.colors[color].root,
|
|
132
|
+
(loading || (href && (disabled || loading))) && "pointer-events-none",
|
|
133
|
+
loading && (showTextWhileLoading ? "[&>*:not([data-icon=loading]):not([data-text])]:hidden" : "[&>*:not([data-icon=loading])]:invisible"),
|
|
134
|
+
className,
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
{isValidElement(IconLeading) && IconLeading}
|
|
138
|
+
{isReactComponent(IconLeading) && <IconLeading data-icon="leading" className={styles.common.icon} />}
|
|
139
|
+
|
|
140
|
+
{loading && (
|
|
141
|
+
<svg
|
|
142
|
+
fill="none"
|
|
143
|
+
data-icon="loading"
|
|
144
|
+
viewBox="0 0 20 20"
|
|
145
|
+
className={cx(styles.common.icon, !showTextWhileLoading && "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2")}
|
|
146
|
+
>
|
|
147
|
+
<circle className="stroke-current opacity-30" cx="10" cy="10" r="8" fill="none" strokeWidth="2" />
|
|
148
|
+
<circle
|
|
149
|
+
className="origin-center animate-spin stroke-current"
|
|
150
|
+
cx="10"
|
|
151
|
+
cy="10"
|
|
152
|
+
r="8"
|
|
153
|
+
fill="none"
|
|
154
|
+
strokeWidth="2"
|
|
155
|
+
strokeDasharray="12.5 50"
|
|
156
|
+
strokeLinecap="round"
|
|
157
|
+
/>
|
|
158
|
+
</svg>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{children && (
|
|
162
|
+
<span data-text className={cx("transition-inherit-all", !noTextPadding && "px-0.5")}>
|
|
163
|
+
{children}
|
|
164
|
+
</span>
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
{isValidElement(IconTrailing) && IconTrailing}
|
|
168
|
+
{isReactComponent(IconTrailing) && <IconTrailing data-icon="trailing" className={styles.common.icon} />}
|
|
169
|
+
</Component>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
import { registerThemeVariant } from '../../../lib/component-registry';
|
|
174
|
+
registerThemeVariant('button', 'aman', Button);
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, DetailedHTMLProps, FC, ReactNode } from "react";
|
|
4
|
+
import React, { isValidElement } from "react";
|
|
5
|
+
import type { ButtonProps as AriaButtonProps } from "react-aria-components";
|
|
6
|
+
import { Button as AriaButton, Link as AriaLink } from "react-aria-components";
|
|
7
|
+
import { cx, sortCx } from '../../../utils/cx';
|
|
8
|
+
import { isReactComponent } from '../../../utils/is-react-component';
|
|
9
|
+
|
|
10
|
+
export const styles = sortCx({
|
|
11
|
+
common: {
|
|
12
|
+
root: [
|
|
13
|
+
"group relative inline-flex h-max cursor-pointer items-center justify-center whitespace-nowrap outline-brand transition duration-100 ease-linear before:absolute focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
14
|
+
// When button is used within `InputGroup`
|
|
15
|
+
"in-data-input-wrapper:shadow-xs in-data-input-wrapper:focus:!z-50 in-data-input-wrapper:in-data-leading:-mr-px in-data-input-wrapper:in-data-leading:rounded-r-none in-data-input-wrapper:in-data-leading:before:rounded-r-none in-data-input-wrapper:in-data-trailing:-ml-px in-data-input-wrapper:in-data-trailing:rounded-l-none in-data-input-wrapper:in-data-trailing:before:rounded-l-none",
|
|
16
|
+
// Disabled styles
|
|
17
|
+
"disabled:cursor-not-allowed disabled:text-fg-disabled",
|
|
18
|
+
// Icon styles
|
|
19
|
+
"disabled:*:data-icon:text-fg-disabled_subtle",
|
|
20
|
+
// Same as `icon` but for SSR icons that cannot be passed to the client as functions.
|
|
21
|
+
"*:data-icon:pointer-events-none *:data-icon:size-5 *:data-icon:shrink-0 *:data-icon:transition-inherit-all",
|
|
22
|
+
].join(" "),
|
|
23
|
+
icon: "pointer-events-none size-5 shrink-0 transition-inherit-all",
|
|
24
|
+
},
|
|
25
|
+
sizes: {
|
|
26
|
+
sm: {
|
|
27
|
+
root: [
|
|
28
|
+
"gap-1 rounded-lg px-3 py-2 text-sm font-semibold before:rounded-[7px] data-icon-only:p-2",
|
|
29
|
+
"in-data-input-wrapper:px-3.5 in-data-input-wrapper:py-2.5 in-data-input-wrapper:data-icon-only:p-2.5",
|
|
30
|
+
].join(" "),
|
|
31
|
+
linkRoot: "gap-1",
|
|
32
|
+
},
|
|
33
|
+
md: {
|
|
34
|
+
root: [
|
|
35
|
+
"gap-1 rounded-lg px-3.5 py-2.5 text-sm font-semibold before:rounded-[7px] data-icon-only:p-2.5",
|
|
36
|
+
"in-data-input-wrapper:gap-1.5 in-data-input-wrapper:px-4 in-data-input-wrapper:text-md in-data-input-wrapper:data-icon-only:p-3",
|
|
37
|
+
].join(" "),
|
|
38
|
+
linkRoot: "gap-1",
|
|
39
|
+
},
|
|
40
|
+
lg: {
|
|
41
|
+
root: "gap-1.5 rounded-lg px-4 py-2.5 text-md font-semibold before:rounded-[7px] data-icon-only:p-3",
|
|
42
|
+
linkRoot: "gap-1.5",
|
|
43
|
+
},
|
|
44
|
+
xl: {
|
|
45
|
+
root: "gap-1.5 rounded-lg px-4.5 py-3 text-md font-semibold before:rounded-[7px] data-icon-only:p-3.5",
|
|
46
|
+
linkRoot: "gap-1.5",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
colors: {
|
|
51
|
+
primary: {
|
|
52
|
+
root: [
|
|
53
|
+
"bg-brand-solid text-white shadow-xs-skeumorphic ring-1 ring-transparent ring-inset hover:bg-brand-solid_hover data-loading:bg-brand-solid_hover",
|
|
54
|
+
// Inner border gradient
|
|
55
|
+
"before:absolute before:inset-px before:border before:border-white/12 before:mask-b-from-0%",
|
|
56
|
+
// Disabled styles
|
|
57
|
+
"disabled:bg-disabled disabled:shadow-xs disabled:ring-disabled_subtle",
|
|
58
|
+
// Icon styles
|
|
59
|
+
"*:data-icon:text-button-primary-icon hover:*:data-icon:text-button-primary-icon_hover",
|
|
60
|
+
].join(" "),
|
|
61
|
+
},
|
|
62
|
+
secondary: {
|
|
63
|
+
root: [
|
|
64
|
+
"bg-primary text-secondary shadow-xs-skeumorphic ring-1 ring-primary ring-inset hover:bg-primary_hover hover:text-secondary_hover data-loading:bg-primary_hover",
|
|
65
|
+
// Disabled styles
|
|
66
|
+
"disabled:shadow-xs disabled:ring-disabled_subtle",
|
|
67
|
+
// Icon styles
|
|
68
|
+
"*:data-icon:text-fg-quaternary hover:*:data-icon:text-fg-quaternary_hover",
|
|
69
|
+
].join(" "),
|
|
70
|
+
},
|
|
71
|
+
tertiary: {
|
|
72
|
+
root: [
|
|
73
|
+
"text-tertiary hover:bg-primary_hover hover:text-tertiary_hover data-loading:bg-primary_hover",
|
|
74
|
+
// Icon styles
|
|
75
|
+
"*:data-icon:text-fg-quaternary hover:*:data-icon:text-fg-quaternary_hover",
|
|
76
|
+
].join(" "),
|
|
77
|
+
},
|
|
78
|
+
"link-gray": {
|
|
79
|
+
root: [
|
|
80
|
+
"justify-normal rounded p-0! text-tertiary hover:text-tertiary_hover",
|
|
81
|
+
// Inner text underline
|
|
82
|
+
"*:data-text:underline *:data-text:decoration-transparent *:data-text:underline-offset-2 hover:*:data-text:decoration-current",
|
|
83
|
+
// Icon styles
|
|
84
|
+
"*:data-icon:text-fg-quaternary hover:*:data-icon:text-fg-quaternary_hover",
|
|
85
|
+
].join(" "),
|
|
86
|
+
},
|
|
87
|
+
"link-color": {
|
|
88
|
+
root: [
|
|
89
|
+
"justify-normal rounded p-0! text-brand-secondary hover:text-brand-secondary_hover",
|
|
90
|
+
// Inner text underline
|
|
91
|
+
"*:data-text:underline *:data-text:decoration-transparent *:data-text:underline-offset-2 hover:*:data-text:decoration-current",
|
|
92
|
+
// Icon styles
|
|
93
|
+
"*:data-icon:text-fg-brand-secondary_alt hover:*:data-icon:text-fg-brand-secondary_hover",
|
|
94
|
+
].join(" "),
|
|
95
|
+
},
|
|
96
|
+
"primary-destructive": {
|
|
97
|
+
root: [
|
|
98
|
+
"bg-error-solid text-white shadow-xs-skeumorphic ring-1 ring-transparent outline-error ring-inset",
|
|
99
|
+
// Inner border gradient
|
|
100
|
+
"before:absolute before:inset-px before:border before:border-white/12 before:mask-b-from-0%",
|
|
101
|
+
// Disabled styles
|
|
102
|
+
"disabled:bg-disabled disabled:shadow-xs disabled:ring-disabled_subtle",
|
|
103
|
+
// Icon styles
|
|
104
|
+
"*:data-icon:text-button-destructive-primary-icon hover:*:data-icon:text-button-destructive-primary-icon_hover",
|
|
105
|
+
].join(" "),
|
|
106
|
+
},
|
|
107
|
+
"secondary-destructive": {
|
|
108
|
+
root: [
|
|
109
|
+
"bg-primary text-error-primary shadow-xs-skeumorphic ring-1 ring-error_subtle outline-error ring-inset hover:bg-error-primary hover:text-error-primary_hover data-loading:bg-error-primary",
|
|
110
|
+
// Disabled styles
|
|
111
|
+
"disabled:bg-primary disabled:shadow-xs disabled:ring-disabled_subtle",
|
|
112
|
+
// Icon styles
|
|
113
|
+
"*:data-icon:text-fg-error-secondary hover:*:data-icon:text-fg-error-primary",
|
|
114
|
+
].join(" "),
|
|
115
|
+
},
|
|
116
|
+
"tertiary-destructive": {
|
|
117
|
+
root: [
|
|
118
|
+
"text-error-primary outline-error hover:bg-error-primary hover:text-error-primary_hover data-loading:bg-error-primary",
|
|
119
|
+
// Icon styles
|
|
120
|
+
"*:data-icon:text-fg-error-secondary hover:*:data-icon:text-fg-error-primary",
|
|
121
|
+
].join(" "),
|
|
122
|
+
},
|
|
123
|
+
"link-destructive": {
|
|
124
|
+
root: [
|
|
125
|
+
"justify-normal rounded p-0! text-error-primary outline-error hover:text-error-primary_hover",
|
|
126
|
+
// Inner text underline
|
|
127
|
+
"*:data-text:underline *:data-text:decoration-transparent *:data-text:underline-offset-2 hover:*:data-text:decoration-current",
|
|
128
|
+
// Icon styles
|
|
129
|
+
"*:data-icon:text-fg-error-secondary hover:*:data-icon:text-fg-error-primary",
|
|
130
|
+
].join(" "),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Common props shared between button and anchor variants
|
|
137
|
+
*/
|
|
138
|
+
export interface CommonProps {
|
|
139
|
+
/** Disables the button and shows a disabled state */
|
|
140
|
+
isDisabled?: boolean;
|
|
141
|
+
/** Shows a loading spinner and disables the button */
|
|
142
|
+
isLoading?: boolean;
|
|
143
|
+
/** The size variant of the button */
|
|
144
|
+
size?: keyof typeof styles.sizes;
|
|
145
|
+
/** The color variant of the button */
|
|
146
|
+
color?: keyof typeof styles.colors;
|
|
147
|
+
/** Icon component or element to show before the text */
|
|
148
|
+
iconLeading?: FC<{ className?: string }> | ReactNode;
|
|
149
|
+
/** Icon component or element to show after the text */
|
|
150
|
+
iconTrailing?: FC<{ className?: string }> | ReactNode;
|
|
151
|
+
/** Removes horizontal padding from the text content */
|
|
152
|
+
noTextPadding?: boolean;
|
|
153
|
+
/** When true, keeps the text visible during loading state */
|
|
154
|
+
showTextWhileLoading?: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Props for the button variant (non-link)
|
|
159
|
+
*/
|
|
160
|
+
export interface ButtonProps extends CommonProps, DetailedHTMLProps<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "color" | "slot">, HTMLButtonElement> {
|
|
161
|
+
/** Slot name for react-aria component */
|
|
162
|
+
slot?: AriaButtonProps["slot"];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Props for the link variant (anchor tag)
|
|
167
|
+
*/
|
|
168
|
+
interface LinkProps extends CommonProps, DetailedHTMLProps<Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "color">, HTMLAnchorElement> {}
|
|
169
|
+
|
|
170
|
+
/** Union type of button and link props */
|
|
171
|
+
export type Props = ButtonProps | LinkProps;
|
|
172
|
+
|
|
173
|
+
export const Button = ({
|
|
174
|
+
size = "sm",
|
|
175
|
+
color = "primary",
|
|
176
|
+
children,
|
|
177
|
+
className,
|
|
178
|
+
noTextPadding,
|
|
179
|
+
iconLeading: IconLeading,
|
|
180
|
+
iconTrailing: IconTrailing,
|
|
181
|
+
isDisabled: disabled,
|
|
182
|
+
isLoading: loading,
|
|
183
|
+
showTextWhileLoading,
|
|
184
|
+
...otherProps
|
|
185
|
+
}: Props) => {
|
|
186
|
+
const href = "href" in otherProps ? otherProps.href : undefined;
|
|
187
|
+
const Component = href ? AriaLink : AriaButton;
|
|
188
|
+
|
|
189
|
+
const isIcon = (IconLeading || IconTrailing) && !children;
|
|
190
|
+
const isLinkType = ["link-gray", "link-color", "link-destructive"].includes(color);
|
|
191
|
+
|
|
192
|
+
noTextPadding = isLinkType || noTextPadding;
|
|
193
|
+
|
|
194
|
+
let props = {};
|
|
195
|
+
|
|
196
|
+
if (href) {
|
|
197
|
+
props = {
|
|
198
|
+
...otherProps,
|
|
199
|
+
|
|
200
|
+
href: disabled ? undefined : href,
|
|
201
|
+
|
|
202
|
+
// Since anchor elements do not support the `disabled` attribute and state,
|
|
203
|
+
// we need to specify `data-rac` and `data-disabled` in order to be able
|
|
204
|
+
// to use the `disabled:` selector in classes.
|
|
205
|
+
...(disabled ? { "data-rac": true, "data-disabled": true } : {}),
|
|
206
|
+
};
|
|
207
|
+
} else {
|
|
208
|
+
props = {
|
|
209
|
+
...otherProps,
|
|
210
|
+
|
|
211
|
+
type: otherProps.type || "button",
|
|
212
|
+
isPending: loading,
|
|
213
|
+
isDisabled: disabled,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<Component
|
|
219
|
+
data-loading={loading ? true : undefined}
|
|
220
|
+
data-icon-only={isIcon ? true : undefined}
|
|
221
|
+
{...props}
|
|
222
|
+
className={cx(
|
|
223
|
+
styles.common.root,
|
|
224
|
+
styles.sizes[size].root,
|
|
225
|
+
styles.colors[color].root,
|
|
226
|
+
isLinkType && styles.sizes[size].linkRoot,
|
|
227
|
+
(loading || (href && (disabled || loading))) && "pointer-events-none",
|
|
228
|
+
// If in `loading` state, hide everything except the loading icon (and text if `showTextWhileLoading` is true).
|
|
229
|
+
loading && (showTextWhileLoading ? "[&>*:not([data-icon=loading]):not([data-text])]:hidden" : "[&>*:not([data-icon=loading])]:invisible"),
|
|
230
|
+
className,
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
233
|
+
{/* Leading icon */}
|
|
234
|
+
{isValidElement(IconLeading) && IconLeading}
|
|
235
|
+
{isReactComponent(IconLeading) && <IconLeading data-icon="leading" className={styles.common.icon} />}
|
|
236
|
+
|
|
237
|
+
{loading && (
|
|
238
|
+
<svg
|
|
239
|
+
fill="none"
|
|
240
|
+
data-icon="loading"
|
|
241
|
+
viewBox="0 0 20 20"
|
|
242
|
+
className={cx(styles.common.icon, !showTextWhileLoading && "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2")}
|
|
243
|
+
>
|
|
244
|
+
{/* Background circle */}
|
|
245
|
+
<circle className="stroke-current opacity-30" cx="10" cy="10" r="8" fill="none" strokeWidth="2" />
|
|
246
|
+
{/* Spinning circle */}
|
|
247
|
+
<circle
|
|
248
|
+
className="origin-center animate-spin stroke-current"
|
|
249
|
+
cx="10"
|
|
250
|
+
cy="10"
|
|
251
|
+
r="8"
|
|
252
|
+
fill="none"
|
|
253
|
+
strokeWidth="2"
|
|
254
|
+
strokeDasharray="12.5 50"
|
|
255
|
+
strokeLinecap="round"
|
|
256
|
+
/>
|
|
257
|
+
</svg>
|
|
258
|
+
)}
|
|
259
|
+
|
|
260
|
+
{children && (
|
|
261
|
+
<span data-text className={cx("transition-inherit-all", !noTextPadding && "px-0.5")}>
|
|
262
|
+
{children}
|
|
263
|
+
</span>
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
{/* Trailing icon */}
|
|
267
|
+
{isValidElement(IconTrailing) && IconTrailing}
|
|
268
|
+
{isReactComponent(IconTrailing) && <IconTrailing data-icon="trailing" className={styles.common.icon} />}
|
|
269
|
+
</Component>
|
|
270
|
+
);
|
|
271
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { X as CloseIcon } from "@untitledui/icons";
|
|
4
|
+
import { Button as AriaButton, type ButtonProps as AriaButtonProps } from "react-aria-components";
|
|
5
|
+
import { cx } from '../../../utils/cx';
|
|
6
|
+
|
|
7
|
+
const sizes = {
|
|
8
|
+
xs: { root: "size-7", icon: "size-4" },
|
|
9
|
+
sm: { root: "size-9", icon: "size-5" },
|
|
10
|
+
md: { root: "size-10", icon: "size-5" },
|
|
11
|
+
lg: { root: "size-11", icon: "size-6" },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const themes = {
|
|
15
|
+
light: "text-fg-quaternary hover:bg-primary_hover hover:text-fg-quaternary_hover focus-visible:outline-2 focus-visible:outline-offset-2 outline-focus-ring",
|
|
16
|
+
dark: "text-fg-white/70 hover:text-fg-white hover:bg-white/20 focus-visible:outline-2 focus-visible:outline-offset-2 outline-focus-ring",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface CloseButtonProps extends AriaButtonProps {
|
|
20
|
+
theme?: "light" | "dark";
|
|
21
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
22
|
+
label?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const CloseButton = ({ label, className, size = "sm", theme = "light", ...otherProps }: CloseButtonProps) => {
|
|
26
|
+
return (
|
|
27
|
+
<AriaButton
|
|
28
|
+
{...otherProps}
|
|
29
|
+
aria-label={label || "Close"}
|
|
30
|
+
className={(state) =>
|
|
31
|
+
cx(
|
|
32
|
+
"flex cursor-pointer items-center justify-center rounded-lg p-2 transition duration-100 ease-linear focus:outline-hidden",
|
|
33
|
+
sizes[size].root,
|
|
34
|
+
themes[theme],
|
|
35
|
+
typeof className === "function" ? className(state) : className,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
<CloseIcon aria-hidden="true" className={cx("shrink-0 transition-inherit-all", sizes[size].icon)} />
|
|
40
|
+
</AriaButton>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentPropsWithRef, FC } from "react";
|
|
4
|
+
import { Button } from '..';
|
|
5
|
+
import { cx } from '../../../utils/cx';
|
|
6
|
+
import { isReactComponent } from '../../../utils/is-react-component';
|
|
7
|
+
|
|
8
|
+
interface RoundButtonProps extends ComponentPropsWithRef<"button"> {
|
|
9
|
+
icon?: FC<{ className?: string }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const RoundButton = ({ icon: Icon, ...props }: RoundButtonProps) => {
|
|
13
|
+
return (
|
|
14
|
+
<Button
|
|
15
|
+
{...props}
|
|
16
|
+
color="link-gray"
|
|
17
|
+
className={cx(
|
|
18
|
+
"group flex size-12 items-center justify-center rounded-full bg-primary ring-1 ring-secondary backdrop-blur transition duration-100 ease-linear ring-inset hover:bg-secondary md:size-14",
|
|
19
|
+
props.className,
|
|
20
|
+
)}
|
|
21
|
+
>
|
|
22
|
+
{props.children ??
|
|
23
|
+
(isReactComponent(Icon) ? (
|
|
24
|
+
<Icon className="size-5 text-fg-quaternary transition-inherit-all group-hover:text-fg-quaternary_hover md:size-6" />
|
|
25
|
+
) : null)}
|
|
26
|
+
</Button>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|