gbs-add-block 1.2.18 → 1.3.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.
Files changed (95) hide show
  1. package/README.md +2 -2
  2. package/index.cjs +28 -7
  3. package/package.json +1 -1
  4. package/source/beta-components/accordion/README.md +45 -0
  5. package/source/beta-components/accordion/__tests__/core.test.ts +58 -0
  6. package/source/beta-components/accordion/core/index.ts +2 -0
  7. package/source/beta-components/accordion/core/open.ts +56 -0
  8. package/source/beta-components/accordion/core/types.ts +17 -0
  9. package/source/beta-components/accordion/index.ts +8 -0
  10. package/source/beta-components/accordion/react/Accordion.tsx +133 -0
  11. package/source/beta-components/accordion/react/AccordionItem.tsx +120 -0
  12. package/source/beta-components/accordion/react/context.ts +20 -0
  13. package/source/beta-components/accordion/react/props.ts +13 -0
  14. package/source/beta-components/accordion/styles.css +208 -0
  15. package/source/beta-components/alert/README.md +39 -0
  16. package/source/beta-components/alert/__tests__/core.test.ts +17 -0
  17. package/source/beta-components/alert/core/index.ts +2 -0
  18. package/source/beta-components/alert/core/live.ts +25 -0
  19. package/source/beta-components/alert/core/types.ts +7 -0
  20. package/source/beta-components/alert/index.ts +5 -0
  21. package/source/beta-components/alert/react/Alert.tsx +113 -0
  22. package/source/beta-components/alert/react/icons.tsx +21 -0
  23. package/source/beta-components/alert/react/locale.ts +5 -0
  24. package/source/beta-components/alert/react/props.ts +11 -0
  25. package/source/beta-components/alert/styles.css +155 -0
  26. package/source/beta-components/avatar/__tests__/core.test.ts +88 -0
  27. package/source/beta-components/avatar/core/identity.ts +103 -0
  28. package/source/beta-components/avatar/core/index.ts +2 -0
  29. package/source/beta-components/avatar/core/types.ts +12 -0
  30. package/source/beta-components/avatar/index.ts +7 -0
  31. package/source/beta-components/avatar/react/Avatar.tsx +118 -0
  32. package/source/beta-components/avatar/react/AvatarGroup.tsx +74 -0
  33. package/source/beta-components/avatar/react/locale.ts +11 -0
  34. package/source/beta-components/avatar/react/props.ts +6 -0
  35. package/source/beta-components/avatar/styles.css +227 -0
  36. package/source/beta-components/badge/README.md +37 -0
  37. package/source/beta-components/badge/__tests__/core.test.ts +46 -0
  38. package/source/beta-components/badge/core/count.ts +37 -0
  39. package/source/beta-components/badge/core/index.ts +2 -0
  40. package/source/beta-components/badge/core/types.ts +11 -0
  41. package/source/beta-components/badge/index.ts +7 -0
  42. package/source/beta-components/badge/react/Badge.tsx +89 -0
  43. package/source/beta-components/badge/react/Tag.tsx +82 -0
  44. package/source/beta-components/badge/react/locale.ts +5 -0
  45. package/source/beta-components/badge/react/props.ts +6 -0
  46. package/source/beta-components/badge/styles.css +204 -0
  47. package/source/beta-components/number-input/README.md +53 -0
  48. package/source/beta-components/number-input/__tests__/core.test.ts +151 -0
  49. package/source/beta-components/number-input/core/format.ts +37 -0
  50. package/source/beta-components/number-input/core/index.ts +4 -0
  51. package/source/beta-components/number-input/core/parse.ts +118 -0
  52. package/source/beta-components/number-input/core/step.ts +73 -0
  53. package/source/beta-components/number-input/core/types.ts +32 -0
  54. package/source/beta-components/number-input/index.ts +5 -0
  55. package/source/beta-components/number-input/react/NumberInput.tsx +465 -0
  56. package/source/beta-components/number-input/react/icons.tsx +17 -0
  57. package/source/beta-components/number-input/react/locale.ts +7 -0
  58. package/source/beta-components/number-input/react/props.ts +11 -0
  59. package/source/beta-components/number-input/styles.css +231 -0
  60. package/source/beta-components/progress/README.md +38 -0
  61. package/source/beta-components/progress/__tests__/core.test.ts +56 -0
  62. package/source/beta-components/progress/core/index.ts +2 -0
  63. package/source/beta-components/progress/core/progress.ts +40 -0
  64. package/source/beta-components/progress/core/types.ts +23 -0
  65. package/source/beta-components/progress/index.ts +7 -0
  66. package/source/beta-components/progress/react/CircularProgress.tsx +106 -0
  67. package/source/beta-components/progress/react/Progress.tsx +113 -0
  68. package/source/beta-components/progress/react/locale.ts +7 -0
  69. package/source/beta-components/progress/react/props.ts +6 -0
  70. package/source/beta-components/progress/styles.css +202 -0
  71. package/source/beta-components/radio-group/README.md +58 -0
  72. package/source/beta-components/radio-group/__tests__/core.test.ts +64 -0
  73. package/source/beta-components/radio-group/core/group.ts +36 -0
  74. package/source/beta-components/radio-group/core/index.ts +2 -0
  75. package/source/beta-components/radio-group/core/types.ts +20 -0
  76. package/source/beta-components/radio-group/index.ts +9 -0
  77. package/source/beta-components/radio-group/react/Radio.tsx +144 -0
  78. package/source/beta-components/radio-group/react/RadioGroup.tsx +179 -0
  79. package/source/beta-components/radio-group/react/context.ts +18 -0
  80. package/source/beta-components/radio-group/react/locale.ts +5 -0
  81. package/source/beta-components/radio-group/react/props.ts +12 -0
  82. package/source/beta-components/radio-group/styles.css +270 -0
  83. package/source/beta-components/shared/__tests__/position.test.ts +143 -170
  84. package/source/beta-components/shared/core/position.ts +2 -15
  85. package/source/beta-components/shared/react/Popover.tsx +5 -18
  86. package/source/beta-components/switch/README.md +46 -0
  87. package/source/beta-components/switch/__tests__/core.test.ts +64 -0
  88. package/source/beta-components/switch/core/index.ts +2 -0
  89. package/source/beta-components/switch/core/toggle.ts +54 -0
  90. package/source/beta-components/switch/core/types.ts +9 -0
  91. package/source/beta-components/switch/index.ts +5 -0
  92. package/source/beta-components/switch/react/Switch.tsx +219 -0
  93. package/source/beta-components/switch/react/locale.ts +5 -0
  94. package/source/beta-components/switch/react/props.ts +12 -0
  95. package/source/beta-components/switch/styles.css +229 -0
@@ -0,0 +1,208 @@
1
+ /*
2
+ * Accordion styles.
3
+ *
4
+ * Each section is a native <details>, so the marker has to be taken off (two
5
+ * ways: `list-item` display in Firefox, the WebKit pseudo-element elsewhere)
6
+ * before a chevron of our own goes on. Tokens read the shared --gbs-*
7
+ * variables when they are set on an ancestor (set them on :root to theme every
8
+ * component at once). Rules live in the `components` layer, so utility classes
9
+ * passed through `className` / `classNames` override them.
10
+ */
11
+ @layer theme, base, components, utilities;
12
+
13
+ @layer components {
14
+ .ac-root {
15
+ --ac-fg: var(--gbs-fg, var(--dg-fg, light-dark(#18181b, #f4f4f5)));
16
+ --ac-muted: var(--gbs-muted, var(--dg-muted, light-dark(#71717a, #a1a1aa)));
17
+ --ac-bg: var(--gbs-bg, var(--dg-bg, light-dark(#ffffff, #0b0b0e)));
18
+ --ac-border: var(--gbs-border, var(--dg-border, light-dark(#e4e4e7, #27272a)));
19
+ --ac-hover: var(--gbs-hover, var(--dg-hover, light-dark(#f4f4f5, #1f1f23)));
20
+ --ac-focus: var(--gbs-focus, var(--dg-focus, light-dark(#2563eb, #60a5fa)));
21
+ --ac-radius: var(--gbs-radius, var(--dg-radius, 8px));
22
+ --ac-font-size: var(--gbs-font-size, var(--dg-font-size, 13px));
23
+ --ac-px: 14px;
24
+ --ac-py: 12px;
25
+ --ac-duration: 180ms;
26
+
27
+ color-scheme: inherit;
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: 8px;
31
+ min-width: 0;
32
+ font-size: var(--ac-font-size);
33
+ line-height: 1.5;
34
+ color: var(--ac-fg);
35
+ }
36
+
37
+ :where(.dark, [data-theme="dark"]) .ac-root {
38
+ color-scheme: dark;
39
+ }
40
+ :where(.light, [data-theme="light"]) .ac-root {
41
+ color-scheme: light;
42
+ }
43
+
44
+ :where(.ac-root) *,
45
+ :where(.ac-root) *::before,
46
+ :where(.ac-root) *::after {
47
+ box-sizing: border-box;
48
+ }
49
+
50
+ .ac-root[data-size="sm"] {
51
+ --ac-px: 10px;
52
+ --ac-py: 8px;
53
+ --ac-font-size: 12px;
54
+ }
55
+ .ac-root[data-size="lg"] {
56
+ --ac-px: 18px;
57
+ --ac-py: 16px;
58
+ --ac-font-size: 14px;
59
+ }
60
+
61
+ /* ---------------------------------------------------------------- item */
62
+
63
+ .ac-item {
64
+ min-width: 0;
65
+ border: 1px solid var(--ac-border);
66
+ border-radius: var(--ac-radius);
67
+ background: var(--ac-bg);
68
+ }
69
+
70
+ /* Contained: one block with dividers instead of separate cards. */
71
+ .ac-root[data-variant="contained"] {
72
+ gap: 0;
73
+ border: 1px solid var(--ac-border);
74
+ border-radius: var(--ac-radius);
75
+ overflow: hidden;
76
+ }
77
+ .ac-root[data-variant="contained"] .ac-item {
78
+ border: 0;
79
+ border-block-end: 1px solid var(--ac-border);
80
+ border-radius: 0;
81
+ }
82
+ .ac-root[data-variant="contained"] .ac-item:last-child {
83
+ border-block-end: 0;
84
+ }
85
+
86
+ /* Plain: rules only, for a sidebar or a FAQ. */
87
+ .ac-root[data-variant="plain"] {
88
+ gap: 0;
89
+ }
90
+ .ac-root[data-variant="plain"] .ac-item {
91
+ border: 0;
92
+ border-block-end: 1px solid var(--ac-border);
93
+ border-radius: 0;
94
+ background: none;
95
+ }
96
+ .ac-root[data-variant="plain"] .ac-header {
97
+ padding-inline: 0;
98
+ }
99
+ .ac-root[data-variant="plain"] .ac-body {
100
+ padding-inline: 0;
101
+ }
102
+
103
+ /* -------------------------------------------------------------- header */
104
+
105
+ .ac-header {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 10px;
109
+ padding: var(--ac-py) var(--ac-px);
110
+ border-radius: inherit;
111
+ cursor: pointer;
112
+ user-select: none;
113
+ /* Firefox draws the triangle through `display: list-item`. */
114
+ list-style: none;
115
+ }
116
+ .ac-header::-webkit-details-marker {
117
+ display: none;
118
+ }
119
+ .ac-header:hover {
120
+ background: var(--ac-hover);
121
+ }
122
+ .ac-header:focus-visible {
123
+ outline: 2px solid var(--ac-focus);
124
+ outline-offset: -2px;
125
+ }
126
+ .ac-item[data-disabled] .ac-header {
127
+ cursor: not-allowed;
128
+ }
129
+ .ac-item[data-disabled] .ac-title {
130
+ color: var(--ac-muted);
131
+ }
132
+
133
+ .ac-heading {
134
+ display: flex;
135
+ flex: 1 1 auto;
136
+ flex-direction: column;
137
+ gap: 1px;
138
+ min-width: 0;
139
+ }
140
+ .ac-title {
141
+ font-weight: 600;
142
+ }
143
+ .ac-description {
144
+ color: var(--ac-muted);
145
+ font-weight: 400;
146
+ }
147
+ .ac-meta {
148
+ display: inline-flex;
149
+ flex: none;
150
+ align-items: center;
151
+ gap: 6px;
152
+ color: var(--ac-muted);
153
+ }
154
+
155
+ .ac-icon {
156
+ display: inline-flex;
157
+ flex: none;
158
+ color: var(--ac-muted);
159
+ transition: rotate var(--ac-duration) ease;
160
+ }
161
+ .ac-icon > svg {
162
+ width: 16px;
163
+ height: 16px;
164
+ }
165
+ .ac-item[open] > .ac-header .ac-icon {
166
+ rotate: 180deg;
167
+ }
168
+
169
+ /* ------------------------------------------------------------- content */
170
+
171
+ .ac-body {
172
+ padding: 0 var(--ac-px) var(--ac-py);
173
+ }
174
+
175
+ /*
176
+ * Opening animates where the browser can do it: `interpolate-size` lets a
177
+ * height of `auto` be interpolated, and `::details-content` is the box to
178
+ * animate. Browsers without either simply show and hide, which is the
179
+ * behaviour everything else falls back to anyway.
180
+ */
181
+ @supports (interpolate-size: allow-keywords) {
182
+ .ac-root {
183
+ interpolate-size: allow-keywords;
184
+ }
185
+ .ac-item::details-content {
186
+ height: 0;
187
+ overflow: hidden;
188
+ transition:
189
+ height var(--ac-duration) ease,
190
+ content-visibility var(--ac-duration) allow-discrete;
191
+ }
192
+ .ac-item[open]::details-content {
193
+ height: auto;
194
+ }
195
+ }
196
+
197
+ @media (prefers-reduced-motion: reduce) {
198
+ .ac-root {
199
+ --ac-duration: 0s;
200
+ }
201
+ }
202
+
203
+ @media (forced-colors: active) {
204
+ .ac-item {
205
+ border: 1px solid CanvasText;
206
+ }
207
+ }
208
+ }
@@ -0,0 +1,39 @@
1
+ # Alert
2
+
3
+ A message that stays in the page: a trial ending, a read-only record, the
4
+ summary above a form that failed validation. For React 19, styled to match the
5
+ rest of the library. No runtime dependencies besides React.
6
+
7
+ ```ts
8
+ import { Alert } from "@/components/alert";
9
+ import "@/components/alert/styles.css";
10
+ ```
11
+
12
+ ```tsx
13
+ <Alert variant="warning" title="Your trial ends in 3 days">
14
+ After that the workspace becomes read-only. <a href="/billing">Add a card</a> to keep it.
15
+ </Alert>
16
+
17
+ <Alert variant="danger" title="We could not save your changes" onDismiss={() => setShown(false)} />
18
+
19
+ <Alert variant="success" size="sm">240 rows imported.</Alert>
20
+ ```
21
+
22
+ **Props:** `variant` (`info` `success` `warning` `danger` `neutral`), `size`
23
+ (`sm` `md`), `title`, `description` or `children`, `icon` (a node, or `false`),
24
+ `actions`, `onDismiss`, `classNames` (`root` `icon` `content` `title`
25
+ `description` `actions` `dismiss`), `localeText`, `ref`, and every `<div>`
26
+ attribute.
27
+
28
+ Which one to reach for:
29
+
30
+ | | Interrupts | Lives | Use for |
31
+ | --- | --- | --- | --- |
32
+ | **Dialog** | Yes, blocks | Top layer | A decision that must happen now |
33
+ | **Toast** | No, and leaves | A corner | Confirming what already happened |
34
+ | **Alert** | No, and stays | In the layout | The state a page or section is in |
35
+
36
+ `liveness(variant)` is exported from the framework-free `core`: problems get
37
+ `role="alert"` and interrupt; everything else gets `role="status"` and waits its
38
+ turn. An alert that was on the page from the start announces nothing either
39
+ way, because a live region only speaks when its contents change.
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { liveness } from "../core/live";
3
+
4
+ describe("liveness", () => {
5
+ it("interrupts for problems", () => {
6
+ // Someone whose card was declined needs to hear it now, not after the
7
+ // paragraph being read finishes.
8
+ expect(liveness("danger")).toEqual({ role: "alert", live: "assertive" });
9
+ expect(liveness("warning")).toEqual({ role: "alert", live: "assertive" });
10
+ });
11
+
12
+ it("waits its turn for everything else", () => {
13
+ for (const variant of ["info", "success", "neutral"] as const) {
14
+ expect(liveness(variant)).toEqual({ role: "status", live: "polite" });
15
+ }
16
+ });
17
+ });
@@ -0,0 +1,2 @@
1
+ export { liveness, type Liveness } from "./live";
2
+ export type * from "./types";
@@ -0,0 +1,25 @@
1
+ import type { AlertVariant } from "./types";
2
+
3
+ export interface Liveness {
4
+ /** `alert` interrupts whatever is being read; `status` waits its turn. */
5
+ role: "alert" | "status";
6
+ live: "assertive" | "polite";
7
+ }
8
+
9
+ /**
10
+ * How loudly an alert announces itself.
11
+ *
12
+ * A banner that appears after the page has loaded is news, and a screen reader
13
+ * has to be told whether it is worth interrupting for. Problems are:
14
+ * `role="alert"` cuts into whatever is being read, which is right for "your
15
+ * card was declined" and rude for "3 rows imported".
16
+ *
17
+ * A banner that was on the page from the start announces nothing either way —
18
+ * a live region only speaks when its contents change — so this only decides
19
+ * what happens when one appears or its text is replaced.
20
+ */
21
+ export function liveness(variant: AlertVariant): Liveness {
22
+ return variant === "danger" || variant === "warning"
23
+ ? { role: "alert", live: "assertive" }
24
+ : { role: "status", live: "polite" };
25
+ }
@@ -0,0 +1,7 @@
1
+ export type AlertVariant = "info" | "success" | "warning" | "danger" | "neutral";
2
+
3
+ export type AlertSize = "sm" | "md";
4
+
5
+ export interface AlertLocaleText {
6
+ dismiss: string;
7
+ }
@@ -0,0 +1,5 @@
1
+ export { Alert } from "./react/Alert";
2
+ export type { AlertProps } from "./react/Alert";
3
+ export { defaultAlertText } from "./react/locale";
4
+ export type { AlertSlot } from "./react/props";
5
+ export * from "./core";
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import {
4
+ useMemo,
5
+ type CSSProperties,
6
+ type HTMLAttributes,
7
+ type ReactNode,
8
+ type Ref,
9
+ } from "react";
10
+ import { liveness } from "../core/live";
11
+ import type { AlertLocaleText, AlertSize, AlertVariant } from "../core/types";
12
+ import { VARIANT_ICONS, XIcon } from "./icons";
13
+ import { defaultAlertText } from "./locale";
14
+ import { cx, type AlertSlot } from "./props";
15
+
16
+ export interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "role"> {
17
+ /** Default `info`. */
18
+ variant?: AlertVariant;
19
+ /** Default `md`. `sm` drops the title to one line of text. */
20
+ size?: AlertSize;
21
+ title?: ReactNode;
22
+ /** The message. `children` works too, and is the better place for links. */
23
+ description?: ReactNode;
24
+ children?: ReactNode;
25
+ /** Replace the variant's glyph, or pass `false` for none. */
26
+ icon?: ReactNode | false;
27
+ /** Buttons under the message: "Try again", "Go to billing". */
28
+ actions?: ReactNode;
29
+ /** Show a close button. The alert does not hide itself; remove it in the handler. */
30
+ onDismiss?(): void;
31
+ className?: string;
32
+ classNames?: Partial<Record<AlertSlot, string>>;
33
+ style?: CSSProperties;
34
+ localeText?: Partial<AlertLocaleText>;
35
+ ref?: Ref<HTMLDivElement>;
36
+ }
37
+
38
+ /**
39
+ * A message that stays in the page: a trial ending, a read-only record, the
40
+ * summary above a form that failed validation.
41
+ *
42
+ * It is the third of three ways to say something, and the only one that is
43
+ * neither interrupting nor temporary. Use a **Dialog** when the message needs a
44
+ * decision before anything else can happen, a **Toast** to confirm something
45
+ * that has already happened, and an Alert for the state a page or section is
46
+ * in.
47
+ */
48
+ export function Alert(props: AlertProps) {
49
+ const {
50
+ ref,
51
+ variant = "info",
52
+ size = "md",
53
+ title,
54
+ description,
55
+ children,
56
+ icon,
57
+ actions,
58
+ onDismiss,
59
+ className,
60
+ classNames,
61
+ style,
62
+ localeText,
63
+ ...rest
64
+ } = props;
65
+
66
+ const text = useMemo(() => ({ ...defaultAlertText, ...localeText }), [localeText]);
67
+ const { role, live } = liveness(variant);
68
+ const Glyph = VARIANT_ICONS[variant];
69
+
70
+ return (
71
+ <div
72
+ {...rest}
73
+ ref={ref}
74
+ // Only what appears after the page has loaded is announced; a banner that
75
+ // was there from the start is read in its turn, like any other text.
76
+ role={role}
77
+ aria-live={live}
78
+ className={cx("al-root", classNames?.root, className)}
79
+ style={style}
80
+ data-variant={variant}
81
+ data-size={size}
82
+ >
83
+ {icon !== false && (
84
+ <span className={cx("al-icon", classNames?.icon)} aria-hidden="true">
85
+ {icon ?? <Glyph />}
86
+ </span>
87
+ )}
88
+
89
+ <div className={cx("al-content", classNames?.content)}>
90
+ {title && <p className={cx("al-title", classNames?.title)}>{title}</p>}
91
+ {(description || children) && (
92
+ <div className={cx("al-description", classNames?.description)}>
93
+ {description}
94
+ {children}
95
+ </div>
96
+ )}
97
+ {actions && <div className={cx("al-actions", classNames?.actions)}>{actions}</div>}
98
+ </div>
99
+
100
+ {onDismiss && (
101
+ <button
102
+ type="button"
103
+ className={cx("al-dismiss", classNames?.dismiss)}
104
+ aria-label={text.dismiss}
105
+ title={text.dismiss}
106
+ onClick={onDismiss}
107
+ >
108
+ <XIcon />
109
+ </button>
110
+ )}
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,21 @@
1
+ import {
2
+ AlertCircleIcon,
3
+ InfoIcon,
4
+ SuccessIcon,
5
+ WarningIcon,
6
+ XIcon as SharedX,
7
+ type IconProps,
8
+ } from "../../shared/react/icons";
9
+ import type { AlertVariant } from "../core/types";
10
+
11
+ /** 14px, to sit in the corner without pulling the eye from the message. */
12
+ export const XIcon = (p: IconProps) => <SharedX width={14} height={14} {...p} />;
13
+
14
+ /** The glyph each variant carries, so colour is never the only signal. */
15
+ export const VARIANT_ICONS: Record<AlertVariant, (props: IconProps) => React.JSX.Element> = {
16
+ info: InfoIcon,
17
+ success: SuccessIcon,
18
+ warning: WarningIcon,
19
+ danger: AlertCircleIcon,
20
+ neutral: InfoIcon,
21
+ };
@@ -0,0 +1,5 @@
1
+ import type { AlertLocaleText } from "../core/types";
2
+
3
+ export const defaultAlertText: AlertLocaleText = {
4
+ dismiss: "Dismiss",
5
+ };
@@ -0,0 +1,11 @@
1
+ export type AlertSlot =
2
+ | "root"
3
+ | "icon"
4
+ | "content"
5
+ | "title"
6
+ | "description"
7
+ | "actions"
8
+ | "dismiss";
9
+
10
+ // Shared with every other component, so class handling cannot drift.
11
+ export { cx } from "../../shared/core/cx";
@@ -0,0 +1,155 @@
1
+ /*
2
+ * Alert styles.
3
+ *
4
+ * A banner that sits in the page, tinted by variant. Tokens read the shared
5
+ * --gbs-* variables when they are set on an ancestor (set them on :root to
6
+ * theme every component at once). Rules live in the `components` layer, so
7
+ * utility classes passed through `className` / `classNames` override them.
8
+ */
9
+ @layer theme, base, components, utilities;
10
+
11
+ @layer components {
12
+ .al-root {
13
+ --al-fg: var(--gbs-fg, var(--dg-fg, light-dark(#18181b, #f4f4f5)));
14
+ --al-muted: var(--gbs-muted, var(--dg-muted, light-dark(#71717a, #a1a1aa)));
15
+ --al-border: var(--gbs-border, var(--dg-border, light-dark(#e4e4e7, #27272a)));
16
+ --al-bg: var(--gbs-bg, var(--dg-bg, light-dark(#ffffff, #0b0b0e)));
17
+ --al-hover: var(--gbs-hover, var(--dg-hover, light-dark(#f4f4f5, #1f1f23)));
18
+ --al-focus: var(--gbs-focus, var(--dg-focus, light-dark(#2563eb, #60a5fa)));
19
+ --al-info: var(--gbs-info, var(--gbs-accent, var(--dg-accent, light-dark(#2563eb, #60a5fa))));
20
+ --al-success: var(--gbs-success, light-dark(#15803d, #4ade80));
21
+ --al-warning: var(--gbs-warning, light-dark(#b45309, #fbbf24));
22
+ --al-danger: var(--gbs-danger, var(--dg-danger, light-dark(#dc2626, #f87171)));
23
+ --al-radius: var(--gbs-radius, var(--dg-radius, 8px));
24
+ --al-font-size: var(--gbs-font-size, var(--dg-font-size, 13px));
25
+ --al-accent: var(--al-info);
26
+ --al-px: 14px;
27
+ --al-py: 12px;
28
+
29
+ color-scheme: inherit;
30
+ position: relative;
31
+ display: flex;
32
+ align-items: flex-start;
33
+ gap: 10px;
34
+ min-width: 0;
35
+ padding: var(--al-py) var(--al-px);
36
+ /* The bar carries the variant at a glance; the tint alone is too faint to
37
+ read as a signal, and colour is never the only one — there is a glyph. */
38
+ border: 1px solid var(--al-border);
39
+ border-inline-start: 3px solid var(--al-accent);
40
+ border-radius: var(--al-radius);
41
+ background: color-mix(in oklab, var(--al-accent) 7%, var(--al-bg));
42
+ font-size: var(--al-font-size);
43
+ line-height: 1.5;
44
+ color: var(--al-fg);
45
+ text-wrap: pretty;
46
+ }
47
+
48
+ :where(.dark, [data-theme="dark"]) .al-root {
49
+ color-scheme: dark;
50
+ }
51
+ :where(.light, [data-theme="light"]) .al-root {
52
+ color-scheme: light;
53
+ }
54
+
55
+ :where(.al-root) *,
56
+ :where(.al-root) *::before,
57
+ :where(.al-root) *::after {
58
+ box-sizing: border-box;
59
+ }
60
+
61
+ .al-root[data-variant="success"] {
62
+ --al-accent: var(--al-success);
63
+ }
64
+ .al-root[data-variant="warning"] {
65
+ --al-accent: var(--al-warning);
66
+ }
67
+ .al-root[data-variant="danger"] {
68
+ --al-accent: var(--al-danger);
69
+ }
70
+ .al-root[data-variant="neutral"] {
71
+ --al-accent: var(--al-muted);
72
+ }
73
+
74
+ .al-root[data-size="sm"] {
75
+ --al-px: 10px;
76
+ --al-py: 8px;
77
+ --al-font-size: 12px;
78
+ gap: 8px;
79
+ }
80
+
81
+ .al-icon {
82
+ display: inline-flex;
83
+ flex: none;
84
+ /* Optically centred on the first line of text rather than its box. */
85
+ margin-top: 1px;
86
+ color: var(--al-accent);
87
+ }
88
+ .al-icon > svg {
89
+ width: 16px;
90
+ height: 16px;
91
+ }
92
+ .al-root[data-size="sm"] .al-icon > svg {
93
+ width: 14px;
94
+ height: 14px;
95
+ }
96
+
97
+ .al-content {
98
+ display: flex;
99
+ flex: 1 1 auto;
100
+ flex-direction: column;
101
+ gap: 2px;
102
+ min-width: 0;
103
+ }
104
+ .al-title {
105
+ margin: 0;
106
+ font-weight: 600;
107
+ }
108
+ .al-description {
109
+ margin: 0;
110
+ /* Not muted: this is the message, not a hint about one. */
111
+ color: inherit;
112
+ }
113
+ .al-description a {
114
+ color: var(--al-accent);
115
+ text-underline-offset: 2px;
116
+ }
117
+ .al-actions {
118
+ display: flex;
119
+ flex-wrap: wrap;
120
+ align-items: center;
121
+ gap: 8px;
122
+ margin-top: 8px;
123
+ }
124
+
125
+ .al-dismiss {
126
+ display: inline-flex;
127
+ flex: none;
128
+ align-items: center;
129
+ justify-content: center;
130
+ width: 22px;
131
+ height: 22px;
132
+ margin: -2px -4px 0 0;
133
+ padding: 0;
134
+ border: 0;
135
+ border-radius: 4px;
136
+ background: none;
137
+ color: var(--al-muted);
138
+ cursor: pointer;
139
+ }
140
+ .al-dismiss:hover {
141
+ background: var(--al-hover);
142
+ color: var(--al-fg);
143
+ }
144
+ .al-dismiss:focus-visible {
145
+ outline: 2px solid var(--al-focus);
146
+ outline-offset: 1px;
147
+ }
148
+
149
+ @media (forced-colors: active) {
150
+ .al-root {
151
+ border: 1px solid CanvasText;
152
+ border-inline-start-width: 3px;
153
+ }
154
+ }
155
+ }