@gtivr4/a1-design-system-react 0.12.1 → 0.14.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/package.json +1 -1
- package/src/components/accordion/Accordion.jsx +2 -0
- package/src/components/banner/Banner.jsx +4 -1
- package/src/components/blockquote/blockquote.css +0 -2
- package/src/components/bottom-drawer/BottomDrawer.jsx +2 -2
- package/src/components/button/Button.d.ts +4 -0
- package/src/components/button/Button.jsx +15 -3
- package/src/components/button/button.css +39 -0
- package/src/components/calendar/calendar.css +0 -2
- package/src/components/card/card.css +1 -0
- package/src/components/checkbox-group/CheckboxGroup.jsx +1 -1
- package/src/components/checkbox-group/checkbox-group.css +3 -3
- package/src/components/choice-group/ChoiceGroup.d.ts +23 -0
- package/src/components/choice-group/ChoiceGroup.jsx +22 -10
- package/src/components/choice-group/choice-group.css +53 -7
- package/src/components/code/Code.d.ts +4 -0
- package/src/components/code/Code.jsx +44 -8
- package/src/components/code/code.css +29 -0
- package/src/components/context-menu/ContextMenu.d.ts +56 -0
- package/src/components/context-menu/ContextMenu.jsx +146 -0
- package/src/components/context-menu/context-menu.css +107 -0
- package/src/components/data-table/DataTable.jsx +1 -1
- package/src/components/definition-list/definition-list.css +15 -0
- package/src/components/divider/Divider.d.ts +4 -2
- package/src/components/divider/Divider.jsx +6 -1
- package/src/components/divider/divider.css +9 -5
- package/src/components/field/DateField.jsx +17 -2
- package/src/components/field/SelectField.jsx +1 -1
- package/src/components/field/TextField.d.ts +2 -0
- package/src/components/field/TextField.jsx +1 -1
- package/src/components/field/TextareaField.jsx +1 -1
- package/src/components/field/TimeField.jsx +17 -2
- package/src/components/field/field.css +12 -5
- package/src/components/field/textarea-field.css +1 -2
- package/src/components/fieldset/fieldset.css +2 -0
- package/src/components/icon-button/IconButton.d.ts +8 -0
- package/src/components/icon-button/IconButton.jsx +9 -4
- package/src/components/inline-editable/InlineEditable.d.ts +25 -0
- package/src/components/inline-editable/InlineEditable.jsx +77 -1
- package/src/components/inline-editable/inline-editable.css +44 -1
- package/src/components/message/Message.jsx +15 -9
- package/src/components/page-layout/page-layout.css +13 -0
- package/src/components/page-nav/page-nav.css +0 -2
- package/src/components/pagination/Pagination.jsx +3 -1
- package/src/components/radio-group/RadioGroup.jsx +1 -1
- package/src/components/radio-group/radio-group.css +3 -3
- package/src/components/section/Section.d.ts +8 -0
- package/src/components/section/Section.jsx +24 -0
- package/src/components/section/section.css +28 -0
- package/src/components/snackbar/Snackbar.d.ts +24 -0
- package/src/components/snackbar/Snackbar.jsx +11 -8
- package/src/components/snackbar/snackbar.css +7 -22
- package/src/components/stack/Stack.jsx +2 -1
- package/src/components/tabs/Tabs.d.ts +2 -0
- package/src/components/tabs/Tabs.jsx +3 -3
- package/src/components/tabs/tabs.css +95 -0
- package/src/components/top-header/TopHeader.jsx +2 -0
- package/src/components/tree-menu/TreeMenu.d.ts +54 -0
- package/src/components/tree-menu/TreeMenu.jsx +500 -0
- package/src/components/tree-menu/tree-menu.css +254 -0
- package/src/index.js +2 -0
- package/src/tokens.css +16 -0
|
@@ -1,7 +1,83 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import "./inline-editable.css";
|
|
3
3
|
|
|
4
|
-
export function InlineEditable({
|
|
4
|
+
export function InlineEditable({ seamless = false, ...props }) {
|
|
5
|
+
// Seamless edits the text in place (contentEditable) so the surrounding
|
|
6
|
+
// component defines all typography; the boxed variant swaps to a field.
|
|
7
|
+
return seamless ? <SeamlessEditable {...props} /> : <BoxEditable {...props} />;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* ── Seamless — edits in place, inheriting all surrounding text styling ────── */
|
|
11
|
+
|
|
12
|
+
function SeamlessEditable({
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
multiline = false,
|
|
16
|
+
disabled = false,
|
|
17
|
+
placeholder,
|
|
18
|
+
className = "",
|
|
19
|
+
inputClassName: _inputClassName,
|
|
20
|
+
children: _children,
|
|
21
|
+
...props
|
|
22
|
+
}) {
|
|
23
|
+
const ref = useRef(null);
|
|
24
|
+
|
|
25
|
+
// Push the value into the DOM, but never while the user is actively typing
|
|
26
|
+
// here — overwriting the text node would reset the caret.
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const el = ref.current;
|
|
29
|
+
if (!el) return;
|
|
30
|
+
const text = value ?? "";
|
|
31
|
+
if (document.activeElement !== el && el.innerText !== text) {
|
|
32
|
+
el.innerText = text;
|
|
33
|
+
}
|
|
34
|
+
}, [value]);
|
|
35
|
+
|
|
36
|
+
function handleInput(event) {
|
|
37
|
+
onChange?.(event.currentTarget.innerText);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleKeyDown(event) {
|
|
41
|
+
if (event.key === "Escape") {
|
|
42
|
+
event.currentTarget.blur();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!multiline && event.key === "Enter") {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
event.currentTarget.blur();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const classes = [
|
|
52
|
+
"a1-inline-editable",
|
|
53
|
+
"a1-inline-editable--seamless",
|
|
54
|
+
multiline && "a1-inline-editable--multiline",
|
|
55
|
+
disabled && "a1-inline-editable--disabled",
|
|
56
|
+
className,
|
|
57
|
+
].filter(Boolean).join(" ");
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<span
|
|
61
|
+
{...props}
|
|
62
|
+
ref={ref}
|
|
63
|
+
className={classes}
|
|
64
|
+
contentEditable={!disabled}
|
|
65
|
+
suppressContentEditableWarning
|
|
66
|
+
role="textbox"
|
|
67
|
+
aria-multiline={multiline || undefined}
|
|
68
|
+
aria-label={props["aria-label"] ?? placeholder}
|
|
69
|
+
aria-disabled={disabled || undefined}
|
|
70
|
+
tabIndex={disabled ? undefined : 0}
|
|
71
|
+
data-placeholder={placeholder}
|
|
72
|
+
onInput={disabled ? undefined : handleInput}
|
|
73
|
+
onKeyDown={disabled ? undefined : handleKeyDown}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ── Boxed — click to reveal a field, commit on blur/Enter ─────────────────── */
|
|
79
|
+
|
|
80
|
+
function BoxEditable({
|
|
5
81
|
value,
|
|
6
82
|
onChange,
|
|
7
83
|
multiline = false,
|
|
@@ -16,8 +16,14 @@
|
|
|
16
16
|
outline-offset: 2px;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/* Disabled — render as plain, selectable text with no interactive affordances */
|
|
19
20
|
.a1-inline-editable--disabled {
|
|
20
|
-
cursor:
|
|
21
|
+
cursor: auto;
|
|
22
|
+
user-select: text;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.a1-inline-editable--disabled:hover {
|
|
26
|
+
outline-color: transparent;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
.a1-inline-editable__placeholder {
|
|
@@ -45,3 +51,40 @@
|
|
|
45
51
|
outline: 2px solid var(--semantic-color-interactive-default);
|
|
46
52
|
outline-offset: -1px;
|
|
47
53
|
}
|
|
54
|
+
|
|
55
|
+
/* ── Seamless ──────────────────────────────────────────────────────────────
|
|
56
|
+
Edits the text in place via contentEditable. The element inherits everything
|
|
57
|
+
— font, size, weight, colour, line-height, alignment, wrapping — from the
|
|
58
|
+
surrounding component (Heading, Paragraph, Button, …), so editing never
|
|
59
|
+
resizes or restyles the text. Only a focus ring is added, for accessibility. */
|
|
60
|
+
.a1-inline-editable--seamless {
|
|
61
|
+
display: inline;
|
|
62
|
+
cursor: text;
|
|
63
|
+
border-radius: var(--base-border-radius-sm);
|
|
64
|
+
outline: none;
|
|
65
|
+
/* allow the caret/selection to sit just outside the glyphs without clipping */
|
|
66
|
+
outline-offset: 2px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Multiline keeps authored line breaks */
|
|
70
|
+
.a1-inline-editable--multiline {
|
|
71
|
+
white-space: pre-wrap;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.a1-inline-editable--seamless:focus,
|
|
75
|
+
.a1-inline-editable--seamless:focus-visible {
|
|
76
|
+
outline: 2px solid var(--semantic-color-interactive-default);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Placeholder while empty (no value typed yet) */
|
|
80
|
+
.a1-inline-editable--seamless:empty::before {
|
|
81
|
+
content: attr(data-placeholder);
|
|
82
|
+
color: var(--semantic-color-text-muted);
|
|
83
|
+
opacity: 0.7;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Disabled seamless — plain, selectable text with no caret affordance */
|
|
87
|
+
.a1-inline-editable--seamless.a1-inline-editable--disabled {
|
|
88
|
+
cursor: auto;
|
|
89
|
+
user-select: text;
|
|
90
|
+
}
|
|
@@ -24,19 +24,23 @@ const ES_SCALE_CONFIG = {
|
|
|
24
24
|
MessageBadge (inline filled status chip)
|
|
25
25
|
═══════════════════════════════════════════════════════════════════════════ */
|
|
26
26
|
|
|
27
|
-
export function MessageBadge({ status = "neutral", subtle = false, size = "md", icon, children }) {
|
|
27
|
+
export function MessageBadge({ status = "neutral", subtle = false, size = "md", icon, className = "", children, ...rest }) {
|
|
28
28
|
const resolvedStatus = STATUSES.includes(status) ? status : "neutral";
|
|
29
29
|
// icon={null} explicitly suppresses the icon; undefined falls back to the status default
|
|
30
30
|
const resolvedIcon = icon === null ? null : (icon ?? STATUS_ICONS[resolvedStatus]);
|
|
31
31
|
|
|
32
32
|
return (
|
|
33
|
-
<span
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
<span
|
|
34
|
+
className={[
|
|
35
|
+
"a1-message-badge",
|
|
36
|
+
`a1-message-badge--${resolvedStatus}`,
|
|
37
|
+
subtle && "a1-message-badge--subtle",
|
|
38
|
+
size === "sm" && "a1-message-badge--sm",
|
|
39
|
+
size === "lg" && "a1-message-badge--lg",
|
|
40
|
+
className,
|
|
41
|
+
].filter(Boolean).join(" ")}
|
|
42
|
+
{...rest}
|
|
43
|
+
>
|
|
40
44
|
{resolvedIcon && <Icon name={resolvedIcon} />}
|
|
41
45
|
{children}
|
|
42
46
|
</span>
|
|
@@ -53,12 +57,14 @@ export function MessageEmptyState({
|
|
|
53
57
|
title,
|
|
54
58
|
description,
|
|
55
59
|
action,
|
|
60
|
+
className = "",
|
|
61
|
+
...rest
|
|
56
62
|
}) {
|
|
57
63
|
const resolvedScale = ES_SCALES.includes(scale) ? scale : "section";
|
|
58
64
|
const { headingAs, headingSize, paragraphSize } = ES_SCALE_CONFIG[resolvedScale];
|
|
59
65
|
|
|
60
66
|
return (
|
|
61
|
-
<div className={`a1-message-empty a1-message-empty--${resolvedScale}`}>
|
|
67
|
+
<div className={`a1-message-empty a1-message-empty--${resolvedScale}${className ? ` ${className}` : ""}`} {...rest}>
|
|
62
68
|
<div className="a1-message-empty__icon-wrap" aria-hidden="true">
|
|
63
69
|
<Icon name={icon} />
|
|
64
70
|
</div>
|
|
@@ -110,6 +110,19 @@
|
|
|
110
110
|
overflow-y: auto;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/* When a SideNav is inside the viewport-height sidebar, the SideNav manages
|
|
114
|
+
its own internal scroll — let it fill the sidebar height exactly so the
|
|
115
|
+
footer stays pinned to the bottom rather than scrolling off screen. */
|
|
116
|
+
.a1-page-layout--viewport-height .a1-page-layout__sidebar:has(.a1-side-nav) {
|
|
117
|
+
overflow-y: hidden;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.a1-page-layout--viewport-height .a1-page-layout__sidebar .a1-side-nav {
|
|
121
|
+
position: relative;
|
|
122
|
+
top: auto;
|
|
123
|
+
height: 100%;
|
|
124
|
+
}
|
|
125
|
+
|
|
113
126
|
.a1-page-layout--viewport-height .a1-page-layout__content {
|
|
114
127
|
overflow: hidden;
|
|
115
128
|
}
|
|
@@ -21,11 +21,13 @@ export function Pagination({
|
|
|
21
21
|
onChange,
|
|
22
22
|
siblings = 1,
|
|
23
23
|
size = "md",
|
|
24
|
+
className = "",
|
|
25
|
+
...rest
|
|
24
26
|
}) {
|
|
25
27
|
const items = getPageItems(page, totalPages, siblings);
|
|
26
28
|
|
|
27
29
|
return (
|
|
28
|
-
<nav aria-label="Pagination" className={`a1-pagination a1-pagination--${size}`}>
|
|
30
|
+
<nav aria-label="Pagination" className={`a1-pagination a1-pagination--${size}${className ? ` ${className}` : ""}`} {...rest}>
|
|
29
31
|
<IconButton
|
|
30
32
|
icon="chevron_left"
|
|
31
33
|
label="Previous page"
|
|
@@ -65,7 +65,7 @@ export function RadioGroup({
|
|
|
65
65
|
<span className="a1-radio-group__legend-inner">
|
|
66
66
|
{label}
|
|
67
67
|
{required && resolvedSize === "comfortable" ? (
|
|
68
|
-
<MessageBadge status="info" subtle>{requiredText}</MessageBadge>
|
|
68
|
+
<MessageBadge status="info" subtle size="sm" icon={null}>{requiredText}</MessageBadge>
|
|
69
69
|
) : required ? (
|
|
70
70
|
<span className="a1-field__asterisk" aria-hidden="true"> *</span>
|
|
71
71
|
) : null}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
--a1-rb-input-nudge: var(--component-radio-group-input-nudge); /* top margin aligning circle center with label cap-height */
|
|
16
16
|
--a1-rb-row-py: var(--component-radio-group-row-padding-block); /* vertical padding on each item row */
|
|
17
17
|
--a1-rb-row-px: var(--component-radio-group-row-padding-inline); /* horizontal padding on each item row */
|
|
18
|
-
--a1-rb-legend-size: var(--semantic-font-size-
|
|
18
|
+
--a1-rb-legend-size: var(--semantic-font-size-form-label-default);
|
|
19
19
|
--a1-rb-label-size: var(--semantic-font-size-body-md);
|
|
20
20
|
--a1-rb-hint-size: var(--semantic-font-size-body-xs);
|
|
21
21
|
--a1-rb-msg-size: var(--semantic-font-size-body-xs);
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
--a1-rb-input-nudge: var(--component-radio-group-comfortable-input-nudge);
|
|
37
37
|
--a1-rb-row-py: var(--component-radio-group-comfortable-row-padding-block);
|
|
38
38
|
--a1-rb-row-px: var(--component-radio-group-comfortable-row-padding-inline);
|
|
39
|
-
--a1-rb-legend-size: var(--semantic-font-size-
|
|
39
|
+
--a1-rb-legend-size: var(--semantic-font-size-form-label-comfortable);
|
|
40
40
|
--a1-rb-label-size: var(--semantic-font-size-body-md);
|
|
41
41
|
--a1-rb-hint-size: var(--semantic-font-size-body-sm);
|
|
42
42
|
--a1-rb-msg-size: var(--semantic-font-size-body-sm);
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
--a1-rb-input-nudge: var(--component-radio-group-compact-input-nudge);
|
|
65
65
|
--a1-rb-row-py: var(--component-radio-group-compact-row-padding-block);
|
|
66
66
|
--a1-rb-row-px: var(--component-radio-group-compact-row-padding-inline);
|
|
67
|
-
--a1-rb-legend-size: var(--semantic-font-size-
|
|
67
|
+
--a1-rb-legend-size: var(--semantic-font-size-form-label-compact);
|
|
68
68
|
--a1-rb-label-size: var(--semantic-font-size-body-sm);
|
|
69
69
|
--a1-rb-hint-size: var(--semantic-font-size-body-xs);
|
|
70
70
|
--a1-rb-msg-size: var(--semantic-font-size-body-xs);
|
|
@@ -27,6 +27,14 @@ export interface SectionProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
27
27
|
height?: "screen" | "hero";
|
|
28
28
|
/** Horizontal layout alignment for direct children. Responsive object syntax supported. */
|
|
29
29
|
align?: ResponsiveAlignment;
|
|
30
|
+
/** Border thickness. Uses the same size tokens as Divider. Omit for no border. */
|
|
31
|
+
borderSize?: "xs" | "sm" | "md" | "lg";
|
|
32
|
+
/** Border pattern. Uses the same line styles as Divider. Default: "solid" */
|
|
33
|
+
borderStyle?: "solid" | "dashed" | "dotted";
|
|
34
|
+
/** Border color tone. Uses the same variants as Divider. Default: "subtle" */
|
|
35
|
+
borderVariant?: "subtle" | "strong" | "accent";
|
|
36
|
+
/** Border radius scale. */
|
|
37
|
+
radius?: "none" | "sm" | "md" | "lg" | "xl";
|
|
30
38
|
children?: React.ReactNode;
|
|
31
39
|
}
|
|
32
40
|
|
|
@@ -20,6 +20,10 @@ const VALID_GRADIENT_POSITIONS = [
|
|
|
20
20
|
const VALID_CONTENT_WIDTHS = ["xs", "sm", "md", "lg", "xl", "2xl"];
|
|
21
21
|
const VALID_HEIGHTS = ["screen", "hero"];
|
|
22
22
|
const VALID_ALIGNMENTS = ["left", "center", "right"];
|
|
23
|
+
const VALID_BORDER_SIZES = ["xs", "sm", "md", "lg"];
|
|
24
|
+
const VALID_BORDER_STYLES = ["solid", "dashed", "dotted"];
|
|
25
|
+
const VALID_BORDER_VARIANTS = ["subtle", "strong", "accent"];
|
|
26
|
+
const VALID_RADII = ["none", "sm", "md", "lg", "xl"];
|
|
23
27
|
|
|
24
28
|
export function Section({
|
|
25
29
|
as: Component = "section",
|
|
@@ -32,6 +36,10 @@ export function Section({
|
|
|
32
36
|
contentWidth,
|
|
33
37
|
height,
|
|
34
38
|
align,
|
|
39
|
+
borderSize,
|
|
40
|
+
borderStyle = "solid",
|
|
41
|
+
borderVariant = "subtle",
|
|
42
|
+
radius,
|
|
35
43
|
className = "",
|
|
36
44
|
children,
|
|
37
45
|
...props
|
|
@@ -88,6 +96,22 @@ export function Section({
|
|
|
88
96
|
classes.push("a1-inverse");
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
if (borderSize && VALID_BORDER_SIZES.includes(borderSize)) {
|
|
100
|
+
classes.push(`a1-section--border-${borderSize}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (borderStyle && VALID_BORDER_STYLES.includes(borderStyle)) {
|
|
104
|
+
classes.push(`a1-section--border-${borderStyle}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (borderVariant && VALID_BORDER_VARIANTS.includes(borderVariant)) {
|
|
108
|
+
classes.push(`a1-section--border-${borderVariant}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (radius && VALID_RADII.includes(radius)) {
|
|
112
|
+
classes.push(`a1-section--radius-${radius}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
91
115
|
if (className) classes.push(className);
|
|
92
116
|
|
|
93
117
|
const innerClasses = [
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
--a1-section-gradient-anchor: center;
|
|
11
11
|
--a1-section-gradient-strength: var(--component-section-gradient-strength);
|
|
12
12
|
--a1-section-justify-items: stretch;
|
|
13
|
+
--a1-section-border-size: 0;
|
|
14
|
+
--a1-section-border-style: solid;
|
|
15
|
+
--a1-section-border-color: transparent;
|
|
16
|
+
border: var(--a1-section-border-size) var(--a1-section-border-style) var(--a1-section-border-color);
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
.a1-section.a1-inverse {
|
|
@@ -23,6 +27,29 @@
|
|
|
23
27
|
.a1-section--surface-panel { --a1-section-surface: var(--semantic-color-surface-panel); background: var(--a1-section-surface); }
|
|
24
28
|
.a1-section--surface-raised { --a1-section-surface: var(--semantic-color-surface-raised); background: var(--a1-section-surface); }
|
|
25
29
|
|
|
30
|
+
/* ── Border ────────────────────────────────────────────────────────────────── */
|
|
31
|
+
|
|
32
|
+
.a1-section--border-xs { --a1-section-border-size: var(--component-divider-size-xs); }
|
|
33
|
+
.a1-section--border-sm { --a1-section-border-size: var(--component-divider-size-sm); }
|
|
34
|
+
.a1-section--border-md { --a1-section-border-size: var(--component-divider-size-md); }
|
|
35
|
+
.a1-section--border-lg { --a1-section-border-size: var(--component-divider-size-lg); }
|
|
36
|
+
|
|
37
|
+
.a1-section--border-solid { --a1-section-border-style: solid; }
|
|
38
|
+
.a1-section--border-dashed { --a1-section-border-style: dashed; }
|
|
39
|
+
.a1-section--border-dotted { --a1-section-border-style: dotted; }
|
|
40
|
+
|
|
41
|
+
.a1-section--border-subtle { --a1-section-border-color: var(--semantic-color-border-subtle); }
|
|
42
|
+
.a1-section--border-strong { --a1-section-border-color: var(--semantic-color-border-strong); }
|
|
43
|
+
.a1-section--border-accent { --a1-section-border-color: var(--semantic-color-text-accent); }
|
|
44
|
+
|
|
45
|
+
/* ── Radius ────────────────────────────────────────────────────────────────── */
|
|
46
|
+
|
|
47
|
+
.a1-section--radius-none { border-radius: 0; }
|
|
48
|
+
.a1-section--radius-sm { border-radius: var(--base-radius-sm); }
|
|
49
|
+
.a1-section--radius-md { border-radius: var(--base-radius-md); }
|
|
50
|
+
.a1-section--radius-lg { border-radius: var(--base-radius-lg); }
|
|
51
|
+
.a1-section--radius-xl { border-radius: var(--base-radius-xl); }
|
|
52
|
+
|
|
26
53
|
/* ── Gradient wash ─────────────────────────────────────────────────────────── */
|
|
27
54
|
|
|
28
55
|
.a1-section--gradient-accent { --a1-section-gradient-color: var(--semantic-color-action-background); }
|
|
@@ -84,6 +111,7 @@
|
|
|
84
111
|
|
|
85
112
|
.a1-section--height-screen {
|
|
86
113
|
min-height: 100svh;
|
|
114
|
+
align-content: start;
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
/* Fills the viewport minus the sticky top header — use for hero/landing sections. */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface SnackbarProps {
|
|
4
|
+
/** Controls visibility — renders nothing when false. Default: false */
|
|
5
|
+
open?: boolean;
|
|
6
|
+
/** Message content displayed inside the snackbar. */
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
/** Label for the optional action button. Both `actionLabel` and `onAction` must be provided to show the button. */
|
|
9
|
+
actionLabel?: string;
|
|
10
|
+
/** Called when the action button is clicked. Both `actionLabel` and `onAction` must be provided to show the button. */
|
|
11
|
+
onAction?: () => void;
|
|
12
|
+
/** Called when the dismiss icon button is clicked. Omit to hide the dismiss button. */
|
|
13
|
+
onClose?: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Snackbar position.
|
|
16
|
+
* Default: "bottom"
|
|
17
|
+
*/
|
|
18
|
+
position?: 'bottom' | 'bottom-left' | 'bottom-right' | 'top' | 'top-left' | 'top-right';
|
|
19
|
+
/** ARIA role. Default: "status" (aria-live="polite"). */
|
|
20
|
+
role?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare function Snackbar(props: SnackbarProps): React.ReactElement | null;
|
|
@@ -2,7 +2,6 @@ import "./snackbar.css";
|
|
|
2
2
|
import { Button } from "../button/Button.jsx";
|
|
3
3
|
import { IconButton } from "../icon-button/IconButton.jsx";
|
|
4
4
|
|
|
5
|
-
const variants = ["default", "success", "info", "warn", "error"];
|
|
6
5
|
const positions = ["bottom", "bottom-left", "bottom-right", "top", "top-left", "top-right"];
|
|
7
6
|
|
|
8
7
|
export function Snackbar({
|
|
@@ -11,21 +10,25 @@ export function Snackbar({
|
|
|
11
10
|
actionLabel,
|
|
12
11
|
onAction,
|
|
13
12
|
onClose,
|
|
14
|
-
variant
|
|
13
|
+
variant: ignoredVariant,
|
|
15
14
|
position = "bottom",
|
|
16
|
-
inverse
|
|
15
|
+
inverse: ignoredInverse,
|
|
17
16
|
role,
|
|
18
17
|
className = "",
|
|
19
18
|
...props
|
|
20
19
|
}) {
|
|
21
20
|
if (!open) return null;
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
// Kept out of the DOM for older call sites; Snackbar now has one visual style.
|
|
23
|
+
void ignoredVariant;
|
|
24
|
+
// Kept out of the DOM for older call sites; inverse is now internal.
|
|
25
|
+
void ignoredInverse;
|
|
26
|
+
|
|
24
27
|
const resolvedPosition = positions.includes(position) ? position : "bottom";
|
|
25
28
|
const classes = [
|
|
26
29
|
"a1-snackbar",
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
"a1-inverse",
|
|
31
|
+
"a1-snackbar--default",
|
|
29
32
|
`a1-snackbar--${resolvedPosition}`,
|
|
30
33
|
className,
|
|
31
34
|
].filter(Boolean).join(" ");
|
|
@@ -33,8 +36,8 @@ export function Snackbar({
|
|
|
33
36
|
return (
|
|
34
37
|
<div
|
|
35
38
|
className={classes}
|
|
36
|
-
role={role ??
|
|
37
|
-
aria-live=
|
|
39
|
+
role={role ?? "status"}
|
|
40
|
+
aria-live="polite"
|
|
38
41
|
{...props}
|
|
39
42
|
>
|
|
40
43
|
<div className="a1-snackbar__content">{children}</div>
|
|
@@ -66,29 +66,14 @@
|
|
|
66
66
|
flex: 1;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
.a1-snackbar--default
|
|
70
|
-
|
|
71
|
-
--a1-snackbar-
|
|
72
|
-
--a1-snackbar-
|
|
73
|
-
--a1-snackbar-foreground: var(--semantic-color-text-default, var(--base-color-neutral-0));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.a1-snackbar--success {
|
|
77
|
-
--a1-snackbar-background: var(--semantic-color-status-success-background);
|
|
78
|
-
--a1-snackbar-border: var(--semantic-color-status-success-border, var(--semantic-color-status-success-background));
|
|
79
|
-
--a1-snackbar-foreground: var(--semantic-color-status-success-foreground);
|
|
80
|
-
}
|
|
69
|
+
.a1-snackbar.a1-snackbar--default {
|
|
70
|
+
--a1-snackbar-background: var(--base-color-neutral-900);
|
|
71
|
+
--a1-snackbar-border: var(--base-color-neutral-900);
|
|
72
|
+
--a1-snackbar-foreground: var(--base-color-neutral-0);
|
|
81
73
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
--a1-snackbar-foreground: var(--semantic-color-status-warn-foreground);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.a1-snackbar--error {
|
|
89
|
-
--a1-snackbar-background: var(--semantic-color-status-error-background);
|
|
90
|
-
--a1-snackbar-border: var(--semantic-color-status-error-border, var(--semantic-color-status-error-background));
|
|
91
|
-
--a1-snackbar-foreground: var(--semantic-color-status-error-foreground);
|
|
74
|
+
background: var(--a1-snackbar-background);
|
|
75
|
+
border-color: var(--a1-snackbar-border);
|
|
76
|
+
color: var(--a1-snackbar-foreground);
|
|
92
77
|
}
|
|
93
78
|
|
|
94
79
|
@media (max-width: 720px) {
|
|
@@ -74,6 +74,7 @@ export function Stack({
|
|
|
74
74
|
justify = "start",
|
|
75
75
|
wrap = false,
|
|
76
76
|
className = "",
|
|
77
|
+
style: styleProp,
|
|
77
78
|
children,
|
|
78
79
|
...props
|
|
79
80
|
}) {
|
|
@@ -89,7 +90,7 @@ export function Stack({
|
|
|
89
90
|
"--a1-stack-wrap": wrap ? "wrap" : "nowrap",
|
|
90
91
|
...getResponsiveDirectionStyle(direction),
|
|
91
92
|
...getResponsiveJustifyStyle(justify),
|
|
92
|
-
...
|
|
93
|
+
...styleProp,
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
return (
|
|
@@ -6,11 +6,11 @@ const TabsContext = createContext(null);
|
|
|
6
6
|
|
|
7
7
|
/* ─── Tabs ─────────────────────────────────────────────────────────────────── */
|
|
8
8
|
|
|
9
|
-
export function Tabs({ children, value, onChange, variant = "line", level = 1, className = "" }) {
|
|
9
|
+
export function Tabs({ children, value, onChange, variant = "line", level = 1, size, className = "" }) {
|
|
10
10
|
const uid = useId();
|
|
11
11
|
return (
|
|
12
|
-
<TabsContext.Provider value={{ value, onChange, variant, level, uid }}>
|
|
13
|
-
<div className={["a1-tabs", `a1-tabs--level-${level}`, className].filter(Boolean).join(" ")}>
|
|
12
|
+
<TabsContext.Provider value={{ value, onChange, variant, level, size, uid }}>
|
|
13
|
+
<div className={["a1-tabs", `a1-tabs--level-${level}`, size && `a1-tabs--${size}`, className].filter(Boolean).join(" ")}>
|
|
14
14
|
{children}
|
|
15
15
|
</div>
|
|
16
16
|
</TabsContext.Provider>
|
|
@@ -225,6 +225,77 @@
|
|
|
225
225
|
border-bottom-left-radius: var(--base-radius-lg);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/* ── Pills variant ──────────────────────────────────────────────────────── */
|
|
229
|
+
|
|
230
|
+
.a1-tab-list-wrapper--pills {
|
|
231
|
+
align-items: center;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.a1-tab-list--pills {
|
|
235
|
+
gap: var(--base-spacing-8);
|
|
236
|
+
flex-wrap: wrap;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.a1-tab--pills {
|
|
240
|
+
padding: var(--component-tab-padding-block) var(--component-tab-padding-inline);
|
|
241
|
+
font-size: var(--semantic-font-size-body-sm);
|
|
242
|
+
font-weight: var(--base-font-weight-medium);
|
|
243
|
+
color: var(--semantic-color-text-muted);
|
|
244
|
+
background: var(--semantic-color-surface-raised);
|
|
245
|
+
border-radius: var(--base-radius-pill);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.a1-tab--pills:hover:not([aria-selected="true"]) {
|
|
249
|
+
color: var(--semantic-color-text-default);
|
|
250
|
+
background: var(--semantic-color-surface-panel);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.a1-tab--pills[aria-selected="true"] {
|
|
254
|
+
color: var(--semantic-color-action-foreground);
|
|
255
|
+
background: var(--semantic-color-action-background);
|
|
256
|
+
cursor: default;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.a1-tab--pills[aria-selected="true"] .a1-tab__count {
|
|
260
|
+
background: color-mix(in srgb, var(--semantic-color-action-foreground) 24%, transparent);
|
|
261
|
+
color: var(--semantic-color-action-foreground);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* ── Segment variant (mirrors SegmentedControl) ─────────────────────────── */
|
|
265
|
+
|
|
266
|
+
.a1-tab-list-wrapper--segment {
|
|
267
|
+
align-items: center;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.a1-tab-list--segment {
|
|
271
|
+
gap: var(--component-segmented-gap);
|
|
272
|
+
padding: var(--component-segmented-padding);
|
|
273
|
+
background: var(--semantic-color-surface-raised);
|
|
274
|
+
border: var(--component-segmented-border-width) solid var(--semantic-color-border-default);
|
|
275
|
+
border-radius: var(--base-radius-control);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.a1-tab--segment {
|
|
279
|
+
padding: var(--component-segmented-segment-padding-block) var(--component-segmented-segment-padding-inline);
|
|
280
|
+
font-size: var(--semantic-font-size-body-sm);
|
|
281
|
+
font-weight: var(--base-font-weight-medium);
|
|
282
|
+
color: var(--semantic-color-text-muted);
|
|
283
|
+
border-radius: calc(var(--base-radius-control) - var(--component-segmented-padding));
|
|
284
|
+
justify-content: center;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.a1-tab--segment:hover:not([aria-selected="true"]) {
|
|
288
|
+
color: var(--semantic-color-text-default);
|
|
289
|
+
background: var(--semantic-color-surface-panel);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.a1-tab--segment[aria-selected="true"] {
|
|
293
|
+
color: var(--semantic-color-text-default);
|
|
294
|
+
background: var(--semantic-color-surface-page);
|
|
295
|
+
box-shadow: var(--semantic-shadow-xs);
|
|
296
|
+
cursor: default;
|
|
297
|
+
}
|
|
298
|
+
|
|
228
299
|
/* ── Progress variant ───────────────────────────────────────────────────── */
|
|
229
300
|
|
|
230
301
|
.a1-tab-list-wrapper--progress {
|
|
@@ -470,3 +541,27 @@
|
|
|
470
541
|
.a1-tab-panel--progress {
|
|
471
542
|
padding: var(--base-spacing-24) 0;
|
|
472
543
|
}
|
|
544
|
+
|
|
545
|
+
/* ─── Compact size ──────────────────────────────────────────────────────────── */
|
|
546
|
+
|
|
547
|
+
.a1-tabs--compact .a1-tab {
|
|
548
|
+
padding: var(--base-spacing-6) var(--base-spacing-8);
|
|
549
|
+
font-size: var(--semantic-font-size-body-sm);
|
|
550
|
+
gap: var(--base-spacing-4);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.a1-tabs--compact .a1-tab--line {
|
|
554
|
+
padding: var(--base-spacing-6) var(--base-spacing-8);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.a1-tabs--compact .a1-tab--folder {
|
|
558
|
+
padding: var(--base-spacing-6) var(--base-spacing-8);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.a1-tabs--compact .a1-tab--pills {
|
|
562
|
+
padding: var(--base-spacing-4) var(--base-spacing-12);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.a1-tabs--compact .a1-tab--segment {
|
|
566
|
+
padding: var(--component-segmented-segment-padding-block-sm) var(--component-segmented-segment-padding-inline-sm);
|
|
567
|
+
}
|
|
@@ -588,6 +588,7 @@ export function TopHeader({
|
|
|
588
588
|
loginButton,
|
|
589
589
|
navIconPosition = "start",
|
|
590
590
|
className = "",
|
|
591
|
+
...rest
|
|
591
592
|
}) {
|
|
592
593
|
const [navMode, setNavMode] = useState(() => resolveNavMode(navIconPosition));
|
|
593
594
|
const [openSubmenu, setOpenSubmenu] = useState(null);
|
|
@@ -641,6 +642,7 @@ export function TopHeader({
|
|
|
641
642
|
navMode === "hidden" && "a1-top-header--nav-hidden",
|
|
642
643
|
className,
|
|
643
644
|
].filter(Boolean).join(" ")}
|
|
645
|
+
{...rest}
|
|
644
646
|
>
|
|
645
647
|
<button
|
|
646
648
|
type="button"
|