@ds-mo/ui 0.1.0
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +969 -0
- package/dist/index.js +2623 -0
- package/dist/index.js.map +1 -0
- package/dist/r/accordion.json +79 -0
- package/dist/r/badge.json +69 -0
- package/dist/r/banner.json +90 -0
- package/dist/r/breadcrumb.json +70 -0
- package/dist/r/button-group.json +65 -0
- package/dist/r/button.json +137 -0
- package/dist/r/card.json +82 -0
- package/dist/r/checkbox.json +91 -0
- package/dist/r/divider.json +57 -0
- package/dist/r/empty-state.json +69 -0
- package/dist/r/error-boundary.json +66 -0
- package/dist/r/fade.json +65 -0
- package/dist/r/field.json +71 -0
- package/dist/r/header.json +67 -0
- package/dist/r/input.json +77 -0
- package/dist/r/loader.json +54 -0
- package/dist/r/menu.json +130 -0
- package/dist/r/modal.json +93 -0
- package/dist/r/pagination.json +80 -0
- package/dist/r/radio.json +85 -0
- package/dist/r/registry.json +1600 -0
- package/dist/r/scrollbar.json +68 -0
- package/dist/r/select.json +93 -0
- package/dist/r/sidebar.json +105 -0
- package/dist/r/skeleton.json +72 -0
- package/dist/r/slider.json +87 -0
- package/dist/r/surface.json +97 -0
- package/dist/r/tab-group.json +72 -0
- package/dist/r/tab.json +71 -0
- package/dist/r/table.json +110 -0
- package/dist/r/tag.json +110 -0
- package/dist/r/text.json +94 -0
- package/dist/r/toast.json +77 -0
- package/dist/r/toggle-button-group.json +65 -0
- package/dist/r/toggle-button.json +94 -0
- package/dist/r/toggle.json +65 -0
- package/dist/r/tooltip.json +86 -0
- package/package.json +80 -0
- package/src/components/Accordion/Accordion.module.css +66 -0
- package/src/components/Accordion/Accordion.stories.tsx +95 -0
- package/src/components/Accordion/Accordion.tsx +122 -0
- package/src/components/Accordion/index.ts +2 -0
- package/src/components/Badge/Badge.module.css +6 -0
- package/src/components/Badge/Badge.stories.tsx +99 -0
- package/src/components/Badge/Badge.tsx +23 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Banner/Banner.module.css +67 -0
- package/src/components/Banner/Banner.stories.tsx +81 -0
- package/src/components/Banner/Banner.tsx +109 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.module.css +46 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +72 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +68 -0
- package/src/components/Breadcrumb/index.ts +2 -0
- package/src/components/Button/Button.module.css +343 -0
- package/src/components/Button/Button.stories.tsx +575 -0
- package/src/components/Button/Button.tsx +201 -0
- package/src/components/Button/index.ts +10 -0
- package/src/components/ButtonGroup/ButtonGroup.module.css +157 -0
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +384 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +122 -0
- package/src/components/ButtonGroup/index.ts +2 -0
- package/src/components/Card/Card.module.css +36 -0
- package/src/components/Card/Card.stories.tsx +78 -0
- package/src/components/Card/Card.tsx +53 -0
- package/src/components/Card/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.module.css +47 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +129 -0
- package/src/components/Checkbox/Checkbox.tsx +68 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/Divider/Divider.module.css +19 -0
- package/src/components/Divider/Divider.stories.tsx +55 -0
- package/src/components/Divider/Divider.tsx +28 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/EmptyState/EmptyState.module.css +8 -0
- package/src/components/EmptyState/EmptyState.stories.tsx +53 -0
- package/src/components/EmptyState/EmptyState.tsx +31 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/ErrorBoundary/ErrorBoundary.module.css +18 -0
- package/src/components/ErrorBoundary/ErrorBoundary.stories.tsx +38 -0
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +50 -0
- package/src/components/ErrorBoundary/index.ts +2 -0
- package/src/components/Fade/Fade.module.css +31 -0
- package/src/components/Fade/Fade.stories.tsx +35 -0
- package/src/components/Fade/Fade.tsx +33 -0
- package/src/components/Fade/index.ts +2 -0
- package/src/components/Field/Field.module.css +5 -0
- package/src/components/Field/Field.stories.tsx +111 -0
- package/src/components/Field/Field.tsx +22 -0
- package/src/components/Field/index.ts +2 -0
- package/src/components/Header/Header.module.css +33 -0
- package/src/components/Header/Header.stories.tsx +52 -0
- package/src/components/Header/Header.tsx +42 -0
- package/src/components/Header/index.ts +2 -0
- package/src/components/Input/Input.module.css +71 -0
- package/src/components/Input/Input.stories.tsx +135 -0
- package/src/components/Input/Input.tsx +76 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/LabelWrap/LabelWrap.module.css +34 -0
- package/src/components/LabelWrap/LabelWrap.tsx +32 -0
- package/src/components/LabelWrap/index.ts +1 -0
- package/src/components/Loader/Loader.module.css +9 -0
- package/src/components/Loader/Loader.stories.tsx +44 -0
- package/src/components/Loader/Loader.tsx +43 -0
- package/src/components/Loader/index.ts +2 -0
- package/src/components/Menu/DestructiveMenuItem.module.css +57 -0
- package/src/components/Menu/DestructiveMenuItem.tsx +64 -0
- package/src/components/Menu/Menu.module.css +49 -0
- package/src/components/Menu/Menu.stories.tsx +129 -0
- package/src/components/Menu/Menu.tsx +269 -0
- package/src/components/Menu/MenuItem.module.css +84 -0
- package/src/components/Menu/MenuItem.tsx +101 -0
- package/src/components/Menu/index.ts +6 -0
- package/src/components/Modal/Modal.module.css +67 -0
- package/src/components/Modal/Modal.stories.tsx +71 -0
- package/src/components/Modal/Modal.tsx +122 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Pagination/Pagination.module.css +64 -0
- package/src/components/Pagination/Pagination.stories.tsx +51 -0
- package/src/components/Pagination/Pagination.tsx +103 -0
- package/src/components/Pagination/index.ts +2 -0
- package/src/components/Radio/Radio.module.css +61 -0
- package/src/components/Radio/Radio.stories.tsx +101 -0
- package/src/components/Radio/Radio.tsx +108 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/Scrollbar/Scrollbar.module.css +81 -0
- package/src/components/Scrollbar/Scrollbar.stories.tsx +46 -0
- package/src/components/Scrollbar/Scrollbar.tsx +187 -0
- package/src/components/Scrollbar/index.ts +2 -0
- package/src/components/Select/Select.module.css +31 -0
- package/src/components/Select/Select.stories.tsx +59 -0
- package/src/components/Select/Select.tsx +92 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.module.css +161 -0
- package/src/components/Sidebar/Sidebar.stories.tsx +125 -0
- package/src/components/Sidebar/Sidebar.tsx +129 -0
- package/src/components/Sidebar/SidebarItem.tsx +57 -0
- package/src/components/Sidebar/SidebarSection.tsx +42 -0
- package/src/components/Sidebar/index.ts +6 -0
- package/src/components/Skeleton/Skeleton.module.css +53 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +89 -0
- package/src/components/Skeleton/Skeleton.tsx +66 -0
- package/src/components/Skeleton/index.ts +2 -0
- package/src/components/Slider/Slider.module.css +62 -0
- package/src/components/Slider/Slider.stories.tsx +120 -0
- package/src/components/Slider/Slider.tsx +74 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/Surface/Surface.module.css +222 -0
- package/src/components/Surface/Surface.stories.tsx +213 -0
- package/src/components/Surface/Surface.tsx +219 -0
- package/src/components/Surface/index.ts +12 -0
- package/src/components/Tab/Tab.module.css +66 -0
- package/src/components/Tab/Tab.stories.tsx +103 -0
- package/src/components/Tab/Tab.tsx +41 -0
- package/src/components/Tab/index.ts +2 -0
- package/src/components/TabGroup/TabGroup.module.css +26 -0
- package/src/components/TabGroup/TabGroup.stories.tsx +60 -0
- package/src/components/TabGroup/TabGroup.tsx +83 -0
- package/src/components/TabGroup/index.ts +2 -0
- package/src/components/Table/Table.module.css +130 -0
- package/src/components/Table/Table.stories.tsx +111 -0
- package/src/components/Table/Table.tsx +226 -0
- package/src/components/Table/index.ts +11 -0
- package/src/components/Tag/Tag.module.css +270 -0
- package/src/components/Tag/Tag.stories.tsx +399 -0
- package/src/components/Tag/Tag.tsx +162 -0
- package/src/components/Tag/index.ts +2 -0
- package/src/components/Text/Text.module.css +166 -0
- package/src/components/Text/Text.stories.tsx +212 -0
- package/src/components/Text/Text.tsx +181 -0
- package/src/components/Text/index.ts +13 -0
- package/src/components/Toast/Toast.module.css +91 -0
- package/src/components/Toast/Toast.stories.tsx +66 -0
- package/src/components/Toast/Toast.tsx +182 -0
- package/src/components/Toast/index.ts +8 -0
- package/src/components/Toggle/Toggle.module.css +87 -0
- package/src/components/Toggle/Toggle.stories.tsx +85 -0
- package/src/components/Toggle/Toggle.tsx +52 -0
- package/src/components/Toggle/index.ts +2 -0
- package/src/components/ToggleButton/ToggleButton.module.css +172 -0
- package/src/components/ToggleButton/ToggleButton.stories.tsx +299 -0
- package/src/components/ToggleButton/ToggleButton.tsx +118 -0
- package/src/components/ToggleButton/index.ts +2 -0
- package/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css +163 -0
- package/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx +341 -0
- package/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx +111 -0
- package/src/components/ToggleButtonGroup/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.module.css +45 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +46 -0
- package/src/components/Tooltip/Tooltip.tsx +224 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/css-modules.d.ts +4 -0
- package/src/docs/ColorUsage.mdx +195 -0
- package/src/docs/ElevationUsage.mdx +95 -0
- package/src/docs/Introduction.mdx +97 -0
- package/src/docs/OpticalSizing.mdx +178 -0
- package/src/docs/TypographyUsage.mdx +121 -0
- package/src/index.ts +154 -0
- package/src/stories/Colors.stories.tsx +528 -0
- package/src/stories/Dimensions.stories.tsx +200 -0
- package/src/stories/Effects.stories.tsx +231 -0
- package/src/stories/Icons.stories.tsx +159 -0
- package/src/stories/Typography.stories.tsx +157 -0
- package/src/types/icons.ts +15 -0
- package/src/types/index.ts +1 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/css-tokens.ts +22 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { cn } from '@/utils/cn';
|
|
3
|
+
import { Text } from '@/components/Text';
|
|
4
|
+
import { LabelWrap } from '@/components/LabelWrap';
|
|
5
|
+
import { Loader } from '@/components/Loader';
|
|
6
|
+
import { ChevronDown } from '@ds-mo/icons';
|
|
7
|
+
import type { IconComponent } from '@/types/icons';
|
|
8
|
+
import styles from './Button.module.css';
|
|
9
|
+
|
|
10
|
+
export type ButtonVariant = 'primary' | 'secondary';
|
|
11
|
+
export type ButtonElevation = 'none' | 'flat' | 'elevated' | 'floating';
|
|
12
|
+
export type ButtonIntent = 'none' | 'neutral' | 'brand' | 'ai' | 'negative' | 'warning' | 'caution' | 'positive';
|
|
13
|
+
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
14
|
+
export type ButtonContrast = 'strong' | 'bold' | 'medium' | 'faint';
|
|
15
|
+
export type ButtonBackground = 'faint' | 'medium' | 'bold' | 'strong' | 'always-dark';
|
|
16
|
+
|
|
17
|
+
export interface ButtonProps {
|
|
18
|
+
/** Polymorphic element type. Defaults to 'button'. */
|
|
19
|
+
as?: React.ElementType;
|
|
20
|
+
/** Visual variant. Defaults to 'primary'. */
|
|
21
|
+
variant?: ButtonVariant;
|
|
22
|
+
/** Label text. */
|
|
23
|
+
label?: string;
|
|
24
|
+
/** Leading icon component from @ds-mo/icons. */
|
|
25
|
+
icon?: IconComponent;
|
|
26
|
+
/** Semantic intent. Defaults to 'brand'. */
|
|
27
|
+
intent?: ButtonIntent;
|
|
28
|
+
/** Size. Defaults to 'md'. */
|
|
29
|
+
size?: ButtonSize;
|
|
30
|
+
/** Pill shape with extra padding. */
|
|
31
|
+
rounded?: boolean;
|
|
32
|
+
/** Fill container width. Label truncates if space is tight. */
|
|
33
|
+
fullWidth?: boolean;
|
|
34
|
+
/** Fixed width. Omit for content width. */
|
|
35
|
+
width?: React.CSSProperties['width'];
|
|
36
|
+
/** Contrast level (primary variant only). Defaults to 'bold'. */
|
|
37
|
+
contrast?: ButtonContrast;
|
|
38
|
+
/**
|
|
39
|
+
* Parent surface context. Adjusts hover tokens and secondary fg colors
|
|
40
|
+
* for buttons placed on colored backgrounds.
|
|
41
|
+
* faint — default, no adjustment
|
|
42
|
+
* medium — use on-medium-background interaction tokens
|
|
43
|
+
* bold — use on-bold-background interaction + inverted fg
|
|
44
|
+
* strong — use on-strong-background interaction + inverted fg
|
|
45
|
+
* always-dark — dark surface (no theme flip), on-bold-background tokens
|
|
46
|
+
*/
|
|
47
|
+
background?: ButtonBackground;
|
|
48
|
+
/** Show trailing ChevronDown dropdown indicator. */
|
|
49
|
+
dropdown?: boolean;
|
|
50
|
+
/** Badge count. */
|
|
51
|
+
badgeCount?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Chrome level for secondary; shadow level for primary.
|
|
54
|
+
* none — ghost (transparent, no border, no shadow)
|
|
55
|
+
* flat — border only, transparent bg [default for secondary]
|
|
56
|
+
* elevated — bg-primary + shadow [default for primary]
|
|
57
|
+
* floating — bg-primary + FAB-strength shadow
|
|
58
|
+
*/
|
|
59
|
+
elevation?: ButtonElevation;
|
|
60
|
+
/** Show loading spinner in place of icon. Prevents interaction. */
|
|
61
|
+
loading?: boolean;
|
|
62
|
+
|
|
63
|
+
onClick?: (e: React.MouseEvent<HTMLElement>) => void;
|
|
64
|
+
onMouseEnter?: React.MouseEventHandler<HTMLElement>;
|
|
65
|
+
onMouseLeave?: React.MouseEventHandler<HTMLElement>;
|
|
66
|
+
className?: string;
|
|
67
|
+
inactive?: boolean;
|
|
68
|
+
type?: 'button' | 'submit' | 'reset';
|
|
69
|
+
id?: string;
|
|
70
|
+
'aria-label'?: string;
|
|
71
|
+
href?: string;
|
|
72
|
+
target?: string;
|
|
73
|
+
rel?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const ICON_SIZE_MAP: Record<ButtonSize, number> = { xs: 12, sm: 16, md: 20, lg: 24 };
|
|
77
|
+
|
|
78
|
+
const TEXT_STYLE_MAP: Record<ButtonSize, string> = {
|
|
79
|
+
xs: 'text-caption-emphasis',
|
|
80
|
+
sm: 'text-body-small-emphasis',
|
|
81
|
+
md: 'text-body-medium-emphasis',
|
|
82
|
+
lg: 'text-body-large-emphasis',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
export const Button = forwardRef<HTMLElement, ButtonProps>(
|
|
87
|
+
(
|
|
88
|
+
{
|
|
89
|
+
as,
|
|
90
|
+
variant = 'primary',
|
|
91
|
+
label,
|
|
92
|
+
icon: Icon,
|
|
93
|
+
intent = 'brand',
|
|
94
|
+
size = 'md',
|
|
95
|
+
rounded = false,
|
|
96
|
+
fullWidth = false,
|
|
97
|
+
width,
|
|
98
|
+
contrast = 'bold',
|
|
99
|
+
background,
|
|
100
|
+
dropdown = false,
|
|
101
|
+
badgeCount,
|
|
102
|
+
elevation,
|
|
103
|
+
loading = false,
|
|
104
|
+
onClick,
|
|
105
|
+
onMouseEnter,
|
|
106
|
+
onMouseLeave,
|
|
107
|
+
className,
|
|
108
|
+
inactive = false,
|
|
109
|
+
type = 'button',
|
|
110
|
+
id,
|
|
111
|
+
'aria-label': ariaLabel,
|
|
112
|
+
href,
|
|
113
|
+
target,
|
|
114
|
+
rel,
|
|
115
|
+
},
|
|
116
|
+
ref
|
|
117
|
+
) => {
|
|
118
|
+
const Component = (as ?? 'button') as React.ElementType;
|
|
119
|
+
const isButton = !as || as === 'button';
|
|
120
|
+
const isInteractive = !inactive && !loading;
|
|
121
|
+
|
|
122
|
+
const hasIcon = !!Icon;
|
|
123
|
+
const hasLabel = !!label;
|
|
124
|
+
const showLeading = hasIcon || loading; // spinner fills the icon slot
|
|
125
|
+
const isIconOnly = showLeading && !hasLabel;
|
|
126
|
+
const isLabelOnly = hasLabel && !showLeading;
|
|
127
|
+
const isIconAndLabel = showLeading && hasLabel;
|
|
128
|
+
const iconSize = ICON_SIZE_MAP[size];
|
|
129
|
+
const textStyle = TEXT_STYLE_MAP[size];
|
|
130
|
+
|
|
131
|
+
// Derive default elevation per variant: primary→none, secondary→flat
|
|
132
|
+
const effectiveElevation: ButtonElevation = elevation ?? (variant === 'primary' ? 'none' : 'flat');
|
|
133
|
+
const elevKey = effectiveElevation.charAt(0).toUpperCase() + effectiveElevation.slice(1);
|
|
134
|
+
|
|
135
|
+
const classes = cn(
|
|
136
|
+
styles.button,
|
|
137
|
+
styles[variant],
|
|
138
|
+
styles[`intent${intent.charAt(0).toUpperCase() + intent.slice(1)}`],
|
|
139
|
+
size !== 'md' && styles[`size${size.toUpperCase()}`],
|
|
140
|
+
rounded && styles.rounded,
|
|
141
|
+
(inactive || loading) && styles.inactive,
|
|
142
|
+
isIconOnly && styles.iconOnly,
|
|
143
|
+
isLabelOnly && styles.labelOnly,
|
|
144
|
+
isIconAndLabel && styles.iconAndLabel,
|
|
145
|
+
dropdown && styles.dropdown,
|
|
146
|
+
variant === 'primary' && intent !== 'none' && contrast !== 'bold' && styles[`contrast${contrast.charAt(0).toUpperCase() + contrast.slice(1)}`],
|
|
147
|
+
styles[`elevation${elevKey}`],
|
|
148
|
+
background && background !== 'faint' && styles[
|
|
149
|
+
background === 'always-dark' ? 'onAlwaysDark'
|
|
150
|
+
: `on${background.charAt(0).toUpperCase() + background.slice(1)}`
|
|
151
|
+
],
|
|
152
|
+
fullWidth && styles.fullWidth,
|
|
153
|
+
className,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const resolvedAriaLabel = ariaLabel || label || 'button';
|
|
157
|
+
|
|
158
|
+
const extraProps: Record<string, unknown> = {};
|
|
159
|
+
if (isButton) {
|
|
160
|
+
extraProps.type = type;
|
|
161
|
+
extraProps.disabled = inactive || loading;
|
|
162
|
+
}
|
|
163
|
+
if (href) extraProps.href = href;
|
|
164
|
+
if (target) extraProps.target = target;
|
|
165
|
+
if (rel) extraProps.rel = rel;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Component
|
|
169
|
+
ref={ref}
|
|
170
|
+
id={id}
|
|
171
|
+
className={classes}
|
|
172
|
+
style={width ? { width } : undefined}
|
|
173
|
+
onClick={isInteractive ? onClick : undefined}
|
|
174
|
+
onMouseEnter={isInteractive ? onMouseEnter : undefined}
|
|
175
|
+
onMouseLeave={isInteractive ? onMouseLeave : undefined}
|
|
176
|
+
aria-label={resolvedAriaLabel}
|
|
177
|
+
aria-busy={loading || undefined}
|
|
178
|
+
{...extraProps}
|
|
179
|
+
>
|
|
180
|
+
{loading ? (
|
|
181
|
+
<Loader size={iconSize} />
|
|
182
|
+
) : (
|
|
183
|
+
Icon && <Icon size={iconSize} />
|
|
184
|
+
)}
|
|
185
|
+
{label && (
|
|
186
|
+
<LabelWrap truncate={fullWidth}>
|
|
187
|
+
<Text variant={textStyle as never} as="span" color="inherit">
|
|
188
|
+
{label}
|
|
189
|
+
</Text>
|
|
190
|
+
</LabelWrap>
|
|
191
|
+
)}
|
|
192
|
+
{dropdown && <ChevronDown size={iconSize} />}
|
|
193
|
+
{badgeCount != null && badgeCount > 0 && (
|
|
194
|
+
<span className={styles.badge}>{badgeCount > 9 ? '+' : badgeCount}</span>
|
|
195
|
+
)}
|
|
196
|
+
</Component>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
Button.displayName = 'Button';
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
ButtonGroup — flush row of Button components
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
.group {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: stretch;
|
|
8
|
+
border-radius: var(--dimension-radius-050);
|
|
9
|
+
position: relative;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Inset ring / highlight overlay — sits above button children so it isn't
|
|
13
|
+
covered by their backgrounds. Outset shadow stays on the element itself. */
|
|
14
|
+
.group::after {
|
|
15
|
+
content: '';
|
|
16
|
+
position: absolute;
|
|
17
|
+
inset: 0;
|
|
18
|
+
border-radius: inherit;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ── Group-level elevation ──────────────────────────────────────────────── */
|
|
23
|
+
/* Individual item chrome is suppressed via .item — the group container
|
|
24
|
+
carries the elevation so it wraps the whole control as one visual unit. */
|
|
25
|
+
|
|
26
|
+
/* Outset shadow on element; inset highlight on ::after (children would cover it otherwise) */
|
|
27
|
+
.groupElevated { box-shadow: var(--effect-shadow-elevated-sm); }
|
|
28
|
+
.groupElevated::after { box-shadow: var(--effect-highlight-elevated-sm); }
|
|
29
|
+
|
|
30
|
+
/* Flat ring lives entirely on ::after — same reasoning as highlight */
|
|
31
|
+
.groupFlat::after { box-shadow: inset 0 0 0 1px var(--color-border-tertiary); }
|
|
32
|
+
|
|
33
|
+
.groupFloating { box-shadow: var(--effect-shadow-elevated-floating); }
|
|
34
|
+
.groupFloating::after { box-shadow: var(--effect-highlight-elevated-floating); }
|
|
35
|
+
/* groupNone = ghost / primary — no shadow, no border */
|
|
36
|
+
|
|
37
|
+
.groupRounded { border-radius: var(--dimension-radius-half); }
|
|
38
|
+
|
|
39
|
+
/* ── Base item ─────────────────────────────────────────────────────────────── */
|
|
40
|
+
|
|
41
|
+
.item {
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
/* Suppress individual elevation so the group reads as one unit */
|
|
44
|
+
box-shadow: none !important;
|
|
45
|
+
/* Suppress noElevation individual border — divider handles separation */
|
|
46
|
+
border: none !important;
|
|
47
|
+
/* Suppress scale-on-press: transform would detach the divider from the
|
|
48
|
+
adjacent button, creating a visible gap. Pressed state is still
|
|
49
|
+
communicated via the ::after overlay colour. */
|
|
50
|
+
}
|
|
51
|
+
.item:active {
|
|
52
|
+
transform: none !important;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── Divider ────────────────────────────────────────────────────────────────
|
|
56
|
+
* Rendered as a separate <div> between items so it is never part of any
|
|
57
|
+
* button's stacking context — disabled (opacity: 0.5) or other state changes
|
|
58
|
+
* on adjacent buttons cannot bleed through to the divider.
|
|
59
|
+
* ─────────────────────────────────────────────────────────────────────────── */
|
|
60
|
+
|
|
61
|
+
/* Full-height divider (secondary + primary).
|
|
62
|
+
* Overlaps each neighbour by 0.5px and paints on top so the alpha token
|
|
63
|
+
* composites over the button fill rather than the page background. */
|
|
64
|
+
.divider {
|
|
65
|
+
width: 1px;
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
background-color: var(--color-border-tertiary);
|
|
68
|
+
position: relative;
|
|
69
|
+
z-index: 1;
|
|
70
|
+
margin: 0 -0.5px;
|
|
71
|
+
pointer-events: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Ghost (elevation=none): short centred divider at icon height */
|
|
75
|
+
.dividerGhost { height: 20px; align-self: center; } /* md default */
|
|
76
|
+
.dividerGhostXS { height: 12px; }
|
|
77
|
+
.dividerGhostSM { height: 16px; }
|
|
78
|
+
.dividerGhostLG { height: 24px; }
|
|
79
|
+
|
|
80
|
+
/* Primary intent: on-background divider colour */
|
|
81
|
+
.dividerBold { background-color: var(--color-border-on-bold-background-tertiary); }
|
|
82
|
+
.dividerMedium { background-color: var(--color-border-on-medium-background-tertiary); }
|
|
83
|
+
.dividerStrong { background-color: var(--color-border-on-strong-background-tertiary); }
|
|
84
|
+
|
|
85
|
+
/* Ghost hover-fade: hide the divider on either side of a hovered ghost button */
|
|
86
|
+
.item:hover + .dividerGhost,
|
|
87
|
+
.dividerGhost:has(+ .item:hover) { opacity: 0; }
|
|
88
|
+
|
|
89
|
+
/* ── Ghost interaction states: rounded chips ──────────────────────────────────
|
|
90
|
+
* Ghost items have no group chrome — hover/press overlay should appear as
|
|
91
|
+
* fully rounded chips (4px all corners). Rounded groups: outer corners of
|
|
92
|
+
* first/last become pill; inner corners stay 4px.
|
|
93
|
+
*
|
|
94
|
+
* Uses .group ancestor (2-class specificity) to reliably beat .first / .last /
|
|
95
|
+
* .middle (1-class) regardless of load order. Explicit longhands throughout to
|
|
96
|
+
* avoid shorthand/longhand cascade ambiguity with the !important rules above.
|
|
97
|
+
* ─────────────────────────────────────────────────────────────────────────── */
|
|
98
|
+
|
|
99
|
+
/* ::after hover/press overlay — all positions: 4px all corners */
|
|
100
|
+
.group .itemShort::after {
|
|
101
|
+
border-top-left-radius: var(--dimension-radius-050) !important;
|
|
102
|
+
border-top-right-radius: var(--dimension-radius-050) !important;
|
|
103
|
+
border-bottom-right-radius: var(--dimension-radius-050) !important;
|
|
104
|
+
border-bottom-left-radius: var(--dimension-radius-050) !important;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ── Border radius: flatten only the inner-facing corners ──────────────────── */
|
|
108
|
+
/* Outer corners keep the button's own radius — works for default + rounded */
|
|
109
|
+
|
|
110
|
+
.first {
|
|
111
|
+
border-top-right-radius: 0 !important;
|
|
112
|
+
border-bottom-right-radius: 0 !important;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.last {
|
|
116
|
+
border-top-left-radius: 0 !important;
|
|
117
|
+
border-bottom-left-radius: 0 !important;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.middle {
|
|
121
|
+
border-radius: 0 !important;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Rounded group: first — left corners become pill (3-class specificity) */
|
|
125
|
+
.groupRounded .first.itemShort::after {
|
|
126
|
+
border-top-left-radius: var(--dimension-radius-half) !important;
|
|
127
|
+
border-bottom-left-radius: var(--dimension-radius-half) !important;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Rounded group: last — right corners become pill (3-class specificity) */
|
|
131
|
+
.groupRounded .last.itemShort::after {
|
|
132
|
+
border-top-right-radius: var(--dimension-radius-half) !important;
|
|
133
|
+
border-bottom-right-radius: var(--dimension-radius-half) !important;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ── Rounded: inner-edge padding correction ────────────────────────────────── */
|
|
137
|
+
/*
|
|
138
|
+
* Rounded buttons add `space-050` extra padding on each side.
|
|
139
|
+
* In a group the inner-facing edge should use base (non-pill) padding instead.
|
|
140
|
+
* These classes are injected by ButtonGroup for first/last/mid × size.
|
|
141
|
+
* XS rounded has no extra padding so needs no correction.
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/* MD — base padding: space-100 */
|
|
145
|
+
.roundedFirstMD { padding-right: var(--dimension-space-100) !important; }
|
|
146
|
+
.roundedLastMD { padding-left: var(--dimension-space-100) !important; }
|
|
147
|
+
.roundedMidMD { padding-left: var(--dimension-space-100) !important; padding-right: var(--dimension-space-100) !important; }
|
|
148
|
+
|
|
149
|
+
/* SM — base padding: space-075 */
|
|
150
|
+
.roundedFirstSM { padding-right: var(--dimension-space-075) !important; }
|
|
151
|
+
.roundedLastSM { padding-left: var(--dimension-space-075) !important; }
|
|
152
|
+
.roundedMidSM { padding-left: var(--dimension-space-075) !important; padding-right: var(--dimension-space-075) !important; }
|
|
153
|
+
|
|
154
|
+
/* LG — base padding: space-150 */
|
|
155
|
+
.roundedFirstLG { padding-right: var(--dimension-space-150) !important; }
|
|
156
|
+
.roundedLastLG { padding-left: var(--dimension-space-150) !important; }
|
|
157
|
+
.roundedMidLG { padding-left: var(--dimension-space-150) !important; padding-right: var(--dimension-space-150) !important; }
|