gbs-add-block 1.2.19 → 2.0.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 (57) hide show
  1. package/README.md +4 -4
  2. package/index.cjs +25 -9
  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/progress/README.md +38 -0
  48. package/source/beta-components/progress/__tests__/core.test.ts +56 -0
  49. package/source/beta-components/progress/core/index.ts +2 -0
  50. package/source/beta-components/progress/core/progress.ts +40 -0
  51. package/source/beta-components/progress/core/types.ts +23 -0
  52. package/source/beta-components/progress/index.ts +7 -0
  53. package/source/beta-components/progress/react/CircularProgress.tsx +106 -0
  54. package/source/beta-components/progress/react/Progress.tsx +113 -0
  55. package/source/beta-components/progress/react/locale.ts +7 -0
  56. package/source/beta-components/progress/react/props.ts +6 -0
  57. package/source/beta-components/progress/styles.css +202 -0
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import { useId, useMemo, type CSSProperties, type ReactNode, type Ref } from "react";
4
+ import { describeProgress } from "../core/progress";
5
+ import type { ProgressLocaleText, ProgressVariant } from "../core/types";
6
+ import { defaultProgressText } from "./locale";
7
+ import { cx, type CircularProgressSlot } from "./props";
8
+
9
+ export interface CircularProgressProps {
10
+ value?: number | null;
11
+ max?: number;
12
+ /** Diameter in pixels. Default 44. */
13
+ size?: number;
14
+ /** Ring thickness in pixels. Default 4. */
15
+ thickness?: number;
16
+ variant?: ProgressVariant;
17
+ /** Names the ring for assistive technology. */
18
+ label?: string;
19
+ /** Print the percentage in the middle, or your own node. */
20
+ showValue?: boolean;
21
+ children?: ReactNode;
22
+ className?: string;
23
+ classNames?: Partial<Record<CircularProgressSlot, string>>;
24
+ style?: CSSProperties;
25
+ localeText?: Partial<ProgressLocaleText>;
26
+ ref?: Ref<HTMLDivElement>;
27
+ }
28
+
29
+ /**
30
+ * The same progress as a ring, for a tile or a table cell where a bar would be
31
+ * too wide.
32
+ *
33
+ * The arc is one circle with a dashed stroke: the dash is the whole
34
+ * circumference, and the gap moves it — no arc paths, no trigonometry, and it
35
+ * scales with `size` without redrawing anything.
36
+ */
37
+ export function CircularProgress(props: CircularProgressProps) {
38
+ const {
39
+ ref,
40
+ value = null,
41
+ max = 100,
42
+ size = 44,
43
+ thickness = 4,
44
+ variant = "accent",
45
+ label,
46
+ showValue = false,
47
+ children,
48
+ className,
49
+ classNames,
50
+ style,
51
+ localeText,
52
+ } = props;
53
+
54
+ const id = useId();
55
+ const text = useMemo(() => ({ ...defaultProgressText, ...localeText }), [localeText]);
56
+ const state = describeProgress(value, max);
57
+
58
+ const radius = (size - thickness) / 2;
59
+ const circumference = 2 * Math.PI * radius;
60
+ // An indeterminate ring shows a quarter arc that spins; a determinate one
61
+ // shows its fraction.
62
+ const filled = state.indeterminate ? 0.25 : state.fraction;
63
+
64
+ return (
65
+ <div
66
+ ref={ref}
67
+ className={cx("pr-circle", classNames?.root, className)}
68
+ style={{ ...style, width: size, height: size }}
69
+ data-variant={variant}
70
+ data-indeterminate={state.indeterminate || undefined}
71
+ role="progressbar"
72
+ aria-valuenow={state.indeterminate ? undefined : state.value ?? undefined}
73
+ aria-valuemin={state.indeterminate ? undefined : 0}
74
+ aria-valuemax={state.indeterminate ? undefined : max}
75
+ aria-valuetext={state.indeterminate ? undefined : text.valueText(state.percent)}
76
+ aria-label={label ?? text.label}
77
+ >
78
+ <svg className={cx("pr-svg", classNames?.svg)} viewBox={`0 0 ${size} ${size}`} aria-hidden="true">
79
+ <circle
80
+ className={cx("pr-circle-track", classNames?.track)}
81
+ cx={size / 2}
82
+ cy={size / 2}
83
+ r={radius}
84
+ fill="none"
85
+ strokeWidth={thickness}
86
+ />
87
+ <circle
88
+ className={cx("pr-circle-bar", classNames?.bar)}
89
+ cx={size / 2}
90
+ cy={size / 2}
91
+ r={radius}
92
+ fill="none"
93
+ strokeWidth={thickness}
94
+ strokeLinecap="round"
95
+ strokeDasharray={circumference}
96
+ strokeDashoffset={circumference * (1 - filled)}
97
+ />
98
+ </svg>
99
+ {(showValue || children) && (
100
+ <span className={cx("pr-circle-label", classNames?.label)} aria-hidden="true" id={`${id}-label`}>
101
+ {children ?? (state.indeterminate ? "" : `${state.percent}%`)}
102
+ </span>
103
+ )}
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import {
4
+ useId,
5
+ useMemo,
6
+ type CSSProperties,
7
+ type HTMLAttributes,
8
+ type ReactNode,
9
+ type Ref,
10
+ } from "react";
11
+ import { describeProgress } from "../core/progress";
12
+ import type { ProgressLocaleText, ProgressSize, ProgressVariant } from "../core/types";
13
+ import { defaultProgressText } from "./locale";
14
+ import { cx, type ProgressSlot } from "./props";
15
+
16
+ export interface ProgressProps extends Omit<HTMLAttributes<HTMLDivElement>, "role"> {
17
+ /** How far along. `null` means the length is not known yet. */
18
+ value?: number | null;
19
+ /** What `value` is measured against. Default 100. */
20
+ max?: number;
21
+ /** Names the bar. Shown above it, and used as the accessible name. */
22
+ label?: ReactNode;
23
+ /** Show the percentage beside the label. */
24
+ showValue?: boolean;
25
+ /** Replace what is shown and read, e.g. "3.2 MB of 8 MB". */
26
+ valueText?: ReactNode;
27
+ /** Default `accent`. */
28
+ variant?: ProgressVariant;
29
+ /** Default `md`. */
30
+ size?: ProgressSize;
31
+ classNames?: Partial<Record<ProgressSlot, string>>;
32
+ localeText?: Partial<ProgressLocaleText>;
33
+ ref?: Ref<HTMLDivElement>;
34
+ }
35
+
36
+ /**
37
+ * A bar for something with a known end: an upload, an import, a quota.
38
+ *
39
+ * Use a **Spinner** when there is nothing to measure and nothing to say about
40
+ * how long it will take. A progress bar that spends its life indeterminate is
41
+ * a spinner drawn as a promise it cannot keep.
42
+ */
43
+ export function Progress(props: ProgressProps) {
44
+ const {
45
+ ref,
46
+ value = null,
47
+ max = 100,
48
+ label,
49
+ showValue = false,
50
+ valueText,
51
+ variant = "accent",
52
+ size = "md",
53
+ className,
54
+ classNames,
55
+ style,
56
+ localeText,
57
+ id: idProp,
58
+ "aria-label": ariaLabel,
59
+ ...rest
60
+ } = props;
61
+
62
+ const reactId = useId();
63
+ const id = idProp ?? reactId;
64
+ const text = useMemo(() => ({ ...defaultProgressText, ...localeText }), [localeText]);
65
+ const state = describeProgress(value, max);
66
+ const shown = valueText ?? (state.indeterminate ? text.working : text.valueText(state.percent));
67
+
68
+ return (
69
+ <div
70
+ {...rest}
71
+ ref={ref}
72
+ id={id}
73
+ className={cx("pr-root", classNames?.root, className)}
74
+ style={style}
75
+ data-variant={variant}
76
+ data-size={size}
77
+ data-indeterminate={state.indeterminate || undefined}
78
+ >
79
+ {(label || showValue) && (
80
+ <div className={cx("pr-header", classNames?.header)}>
81
+ {label && (
82
+ <span id={`${id}-label`} className={cx("pr-label", classNames?.label)}>
83
+ {label}
84
+ </span>
85
+ )}
86
+ {showValue && <span className={cx("pr-value", classNames?.value)}>{shown}</span>}
87
+ </div>
88
+ )}
89
+
90
+ <div
91
+ className={cx("pr-track", classNames?.track)}
92
+ role="progressbar"
93
+ // No aria-valuenow at all while the length is unknown: that is what
94
+ // tells a screen reader to say "busy" rather than to read out a zero.
95
+ aria-valuenow={state.indeterminate ? undefined : state.value ?? undefined}
96
+ aria-valuemin={state.indeterminate ? undefined : 0}
97
+ aria-valuemax={state.indeterminate ? undefined : max}
98
+ aria-valuetext={state.indeterminate ? undefined : typeof shown === "string" ? shown : undefined}
99
+ // The visible label names the bar when there is one, so the name and
100
+ // what is on screen can never drift apart.
101
+ aria-labelledby={label ? `${id}-label` : undefined}
102
+ aria-label={label ? undefined : (ariaLabel ?? text.label)}
103
+ >
104
+ <div
105
+ className={cx("pr-bar", classNames?.bar)}
106
+ // Drawn from the unrounded fraction, so the bar does not reach the
107
+ // end while the last of the work is still going.
108
+ style={{ "--pr-fraction": state.fraction } as CSSProperties}
109
+ />
110
+ </div>
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,7 @@
1
+ import type { ProgressLocaleText } from "../core/types";
2
+
3
+ export const defaultProgressText: ProgressLocaleText = {
4
+ label: "Progress",
5
+ valueText: (percent: number) => `${percent}%`,
6
+ working: "Working\u2026",
7
+ };
@@ -0,0 +1,6 @@
1
+ export type ProgressSlot = "root" | "header" | "label" | "value" | "track" | "bar";
2
+
3
+ export type CircularProgressSlot = "root" | "svg" | "track" | "bar" | "label";
4
+
5
+ // Shared with every other component, so class handling cannot drift.
6
+ export { cx } from "../../shared/core/cx";
@@ -0,0 +1,202 @@
1
+ /*
2
+ * Progress and CircularProgress styles.
3
+ *
4
+ * The bar is drawn from --pr-fraction (0 to 1) rather than a width in percent,
5
+ * so the same number drives the bar, the ring and any transition. Tokens read
6
+ * the shared --gbs-* variables when they are set on an ancestor (set them on
7
+ * :root to theme every component at once). Rules live in the `components`
8
+ * layer, so utility classes passed through `className` / `classNames` override
9
+ * them.
10
+ */
11
+ @layer theme, base, components, utilities;
12
+
13
+ @layer components {
14
+ .pr-root,
15
+ .pr-circle {
16
+ --pr-fg: var(--gbs-fg, var(--dg-fg, light-dark(#18181b, #f4f4f5)));
17
+ --pr-muted: var(--gbs-muted, var(--dg-muted, light-dark(#71717a, #a1a1aa)));
18
+ --pr-track: var(--gbs-subtle, light-dark(#e4e4e7, #27272a));
19
+ --pr-accent: var(--gbs-accent, var(--dg-accent, light-dark(#2563eb, #60a5fa)));
20
+ --pr-success: var(--gbs-success, light-dark(#15803d, #4ade80));
21
+ --pr-warning: var(--gbs-warning, light-dark(#b45309, #fbbf24));
22
+ --pr-danger: var(--gbs-danger, var(--dg-danger, light-dark(#dc2626, #f87171)));
23
+ --pr-font-size: var(--gbs-font-size, var(--dg-font-size, 13px));
24
+ --pr-height: 8px;
25
+ --pr-duration: 240ms;
26
+ --pr-bar: var(--pr-accent);
27
+
28
+ color-scheme: inherit;
29
+ font-size: var(--pr-font-size);
30
+ color: var(--pr-fg);
31
+ }
32
+
33
+ :where(.dark, [data-theme="dark"]) :is(.pr-root, .pr-circle) {
34
+ color-scheme: dark;
35
+ }
36
+ :where(.light, [data-theme="light"]) :is(.pr-root, .pr-circle) {
37
+ color-scheme: light;
38
+ }
39
+
40
+ :where(.pr-root, .pr-circle) *,
41
+ :where(.pr-root, .pr-circle) *::before,
42
+ :where(.pr-root, .pr-circle) *::after {
43
+ box-sizing: border-box;
44
+ }
45
+
46
+ .pr-root[data-variant="success"],
47
+ .pr-circle[data-variant="success"] {
48
+ --pr-bar: var(--pr-success);
49
+ }
50
+ .pr-root[data-variant="warning"],
51
+ .pr-circle[data-variant="warning"] {
52
+ --pr-bar: var(--pr-warning);
53
+ }
54
+ .pr-root[data-variant="danger"],
55
+ .pr-circle[data-variant="danger"] {
56
+ --pr-bar: var(--pr-danger);
57
+ }
58
+
59
+ /* ------------------------------------------------------------------ bar */
60
+
61
+ .pr-root {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 6px;
65
+ min-width: 0;
66
+ }
67
+ .pr-root[data-size="sm"] {
68
+ --pr-height: 4px;
69
+ --pr-font-size: 12px;
70
+ }
71
+ .pr-root[data-size="lg"] {
72
+ --pr-height: 12px;
73
+ }
74
+
75
+ .pr-header {
76
+ display: flex;
77
+ align-items: baseline;
78
+ justify-content: space-between;
79
+ gap: 12px;
80
+ min-width: 0;
81
+ }
82
+ .pr-label {
83
+ overflow: hidden;
84
+ font-weight: 500;
85
+ text-overflow: ellipsis;
86
+ white-space: nowrap;
87
+ }
88
+ .pr-value {
89
+ flex: none;
90
+ color: var(--pr-muted);
91
+ font-variant-numeric: tabular-nums;
92
+ }
93
+
94
+ .pr-track {
95
+ position: relative;
96
+ width: 100%;
97
+ height: var(--pr-height);
98
+ border-radius: 999px;
99
+ background: var(--pr-track);
100
+ overflow: hidden;
101
+ }
102
+
103
+ .pr-bar {
104
+ width: 100%;
105
+ height: 100%;
106
+ border-radius: inherit;
107
+ background: var(--pr-bar);
108
+ /* `scaleX` from the origin: the compositor handles it, so a bar that ticks
109
+ up every few milliseconds costs nothing in layout. */
110
+ transform-origin: left center;
111
+ scale: var(--pr-fraction, 0) 1;
112
+ transition: scale var(--pr-duration) ease;
113
+ }
114
+ .pr-root:dir(rtl) .pr-bar {
115
+ transform-origin: right center;
116
+ }
117
+
118
+ /* With no length to show, a short bar sweeps the track instead. */
119
+ .pr-root[data-indeterminate] .pr-bar {
120
+ width: 35%;
121
+ scale: 1 1;
122
+ animation: pr-sweep 1.3s ease-in-out infinite;
123
+ }
124
+ @keyframes pr-sweep {
125
+ from {
126
+ translate: -100%;
127
+ }
128
+ to {
129
+ translate: 300%;
130
+ }
131
+ }
132
+
133
+ /* ----------------------------------------------------------------- ring */
134
+
135
+ .pr-circle {
136
+ position: relative;
137
+ display: inline-flex;
138
+ flex: none;
139
+ align-items: center;
140
+ justify-content: center;
141
+ vertical-align: middle;
142
+ }
143
+ .pr-svg {
144
+ width: 100%;
145
+ height: 100%;
146
+ /* Zero is at three o'clock in SVG; a progress ring starts at the top. */
147
+ rotate: -90deg;
148
+ }
149
+ .pr-circle-track {
150
+ stroke: var(--pr-track);
151
+ }
152
+ .pr-circle-bar {
153
+ stroke: var(--pr-bar);
154
+ transition: stroke-dashoffset var(--pr-duration) ease;
155
+ }
156
+ .pr-circle[data-indeterminate] .pr-svg {
157
+ animation: pr-spin 1s linear infinite;
158
+ }
159
+ @keyframes pr-spin {
160
+ from {
161
+ rotate: -90deg;
162
+ }
163
+ to {
164
+ rotate: 270deg;
165
+ }
166
+ }
167
+
168
+ .pr-circle-label {
169
+ position: absolute;
170
+ font-size: calc(var(--pr-font-size) - 1px);
171
+ font-variant-numeric: tabular-nums;
172
+ font-weight: 600;
173
+ line-height: 1;
174
+ }
175
+
176
+ @media (prefers-reduced-motion: reduce) {
177
+ .pr-bar,
178
+ .pr-circle-bar {
179
+ transition: none;
180
+ }
181
+ /* Movement that repeats forever is what reduced motion is about, but a
182
+ stopped indeterminate bar would look broken — so it slows instead. */
183
+ .pr-root[data-indeterminate] .pr-bar {
184
+ animation-duration: 3s;
185
+ }
186
+ .pr-circle[data-indeterminate] .pr-svg {
187
+ animation-duration: 2.5s;
188
+ }
189
+ }
190
+
191
+ @media (forced-colors: active) {
192
+ .pr-track {
193
+ border: 1px solid CanvasText;
194
+ }
195
+ .pr-bar {
196
+ background: Highlight;
197
+ }
198
+ .pr-circle-bar {
199
+ stroke: Highlight;
200
+ }
201
+ }
202
+ }