@surflane/ui 0.0.0-stage → 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.
Files changed (89) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/LICENSE-MIT +18 -0
  3. package/README.md +55 -2
  4. package/dist/advanced-forms.d.ts +115 -0
  5. package/dist/advanced-forms.js +93 -0
  6. package/dist/app-shell.d.ts +23 -0
  7. package/dist/app-shell.js +29 -0
  8. package/dist/button.d.ts +11 -0
  9. package/dist/button.js +16 -0
  10. package/dist/chart.d.ts +48 -0
  11. package/dist/chart.js +124 -0
  12. package/dist/commands.d.ts +47 -0
  13. package/dist/commands.js +27 -0
  14. package/dist/content.d.ts +59 -0
  15. package/dist/content.js +66 -0
  16. package/dist/context-overlays.d.ts +95 -0
  17. package/dist/context-overlays.js +50 -0
  18. package/dist/data.d.ts +95 -0
  19. package/dist/data.js +40 -0
  20. package/dist/date-picker.d.ts +23 -0
  21. package/dist/date-picker.js +39 -0
  22. package/dist/dialog-focus.d.ts +5 -0
  23. package/dist/dialog-focus.js +35 -0
  24. package/dist/dialog.d.ts +59 -0
  25. package/dist/dialog.js +49 -0
  26. package/dist/feedback.d.ts +76 -0
  27. package/dist/feedback.js +35 -0
  28. package/dist/file-upload.d.ts +77 -0
  29. package/dist/file-upload.js +343 -0
  30. package/dist/filter-bar.d.ts +17 -0
  31. package/dist/filter-bar.js +23 -0
  32. package/dist/form-state.d.ts +5 -0
  33. package/dist/form-state.js +63 -0
  34. package/dist/forms.d.ts +91 -0
  35. package/dist/forms.js +109 -0
  36. package/dist/foundations.d.ts +51 -0
  37. package/dist/foundations.js +55 -0
  38. package/dist/icon.d.ts +37 -0
  39. package/dist/icon.js +28 -0
  40. package/dist/index.d.ts +37 -0
  41. package/dist/index.js +38 -0
  42. package/dist/input-extensions.d.ts +117 -0
  43. package/dist/input-extensions.js +64 -0
  44. package/dist/input.d.ts +7 -0
  45. package/dist/input.js +10 -0
  46. package/dist/markdown.d.ts +15 -0
  47. package/dist/markdown.js +121 -0
  48. package/dist/menu.d.ts +79 -0
  49. package/dist/menu.js +44 -0
  50. package/dist/messages.d.ts +63 -0
  51. package/dist/messages.js +38 -0
  52. package/dist/navigation-menu.d.ts +52 -0
  53. package/dist/navigation-menu.js +31 -0
  54. package/dist/navigation.d.ts +107 -0
  55. package/dist/navigation.js +108 -0
  56. package/dist/otp-field.d.ts +12 -0
  57. package/dist/otp-field.js +46 -0
  58. package/dist/pagination.d.ts +23 -0
  59. package/dist/pagination.js +44 -0
  60. package/dist/panels.d.ts +66 -0
  61. package/dist/panels.js +99 -0
  62. package/dist/parts.d.ts +9 -0
  63. package/dist/parts.js +22 -0
  64. package/dist/popover.d.ts +21 -0
  65. package/dist/popover.js +13 -0
  66. package/dist/scroll-area.d.ts +8 -0
  67. package/dist/scroll-area.js +39 -0
  68. package/dist/select.d.ts +33 -0
  69. package/dist/select.js +97 -0
  70. package/dist/sizing.d.ts +13 -0
  71. package/dist/sizing.js +23 -0
  72. package/dist/slot.d.ts +2 -0
  73. package/dist/slot.js +10 -0
  74. package/dist/status.d.ts +22 -0
  75. package/dist/status.js +17 -0
  76. package/dist/syntax-engine.d.ts +5 -0
  77. package/dist/syntax-engine.js +129 -0
  78. package/dist/syntax-highlighter.d.ts +17 -0
  79. package/dist/syntax-highlighter.js +64 -0
  80. package/dist/toast.d.ts +59 -0
  81. package/dist/toast.js +99 -0
  82. package/dist/tone-transition.d.ts +4 -0
  83. package/dist/tone-transition.js +81 -0
  84. package/dist/tooltip.d.ts +11 -0
  85. package/dist/tooltip.js +13 -0
  86. package/dist/tree.d.ts +28 -0
  87. package/dist/tree.js +311 -0
  88. package/package.json +109 -4
  89. package/src/styles.css +3708 -0
package/dist/select.js ADDED
@@ -0,0 +1,97 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useComponentSize } from "./sizing.js";
5
+ import { Select as BaseSelect } from "@base-ui/react/select";
6
+ const cx = (...v) => v.filter(Boolean).join(" ");
7
+ function Chevron() {
8
+ return (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.7", "aria-hidden": "true", children: _jsx("path", { d: "m7 10 5 5 5-5" }) }));
9
+ }
10
+ function Check() {
11
+ return (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.7", "aria-hidden": "true", children: _jsx("path", { d: "m5 12 4 4L19 6" }) }));
12
+ }
13
+ export function Select({ options, value, defaultValue, onValueChange, className, inputRef, triggerRef, size, ...props }) {
14
+ const scheme = useComponentSize(size);
15
+ const [localValue, setLocalValue] = React.useState(defaultValue ?? options[0]?.value ?? "");
16
+ const nativeInput = React.useRef(null);
17
+ const initialValue = defaultValue ?? options[0]?.value ?? "";
18
+ const resetCallback = React.useRef(onValueChange);
19
+ resetCallback.current = onValueChange;
20
+ React.useEffect(() => {
21
+ const owner = nativeInput.current?.form;
22
+ if (!owner)
23
+ return;
24
+ const pending = new Set();
25
+ const reset = (event) => {
26
+ const timer = setTimeout(() => {
27
+ pending.delete(timer);
28
+ if (event.defaultPrevented)
29
+ return;
30
+ setLocalValue(initialValue);
31
+ resetCallback.current?.(initialValue);
32
+ }, 0);
33
+ pending.add(timer);
34
+ };
35
+ owner.addEventListener("reset", reset);
36
+ return () => {
37
+ owner.removeEventListener("reset", reset);
38
+ pending.forEach(clearTimeout);
39
+ };
40
+ }, [initialValue, props.form]);
41
+ const selected = value ?? localValue;
42
+ const { name, form, required, disabled, id, ...aria } = props;
43
+ return (_jsx("span", { className: cx("pui-select control-shell select-control", className), "data-size": size, "data-ui-size": scheme, "data-disabled": disabled || undefined, children: _jsxs(BaseSelect.Root, { items: options, value: selected, onValueChange: (v) => {
44
+ setLocalValue(v ?? "");
45
+ onValueChange?.(v ?? "");
46
+ }, inputRef: (node) => {
47
+ nativeInput.current = node;
48
+ if (typeof inputRef === "function")
49
+ inputRef(node);
50
+ else if (inputRef)
51
+ inputRef.current = node;
52
+ }, name: name, form: form, required: required, disabled: disabled, id: id, children: [_jsxs(BaseSelect.Trigger, { ...aria, ref: triggerRef, className: "pui-select-trigger form-control", "data-value": selected, children: [_jsx(BaseSelect.Value, {}), _jsx(BaseSelect.Icon, { className: "pui-select-icon select-chevron", children: _jsx(Chevron, {}) })] }), _jsx(BaseSelect.Portal, { children: _jsx(BaseSelect.Positioner, { className: "pui-positioner", sideOffset: 6, alignItemWithTrigger: false, children: _jsx(BaseSelect.Popup, { className: "pui-select-popup", "data-ui-size": scheme, children: _jsx(BaseSelect.List, { children: options.map((o) => (_jsxs(BaseSelect.Item, { className: "pui-select-item", value: o.value, "data-option-value": o.value, disabled: o.disabled, children: [_jsx(BaseSelect.ItemIndicator, { className: "pui-select-check", children: _jsx(Check, {}) }), _jsx(BaseSelect.ItemText, { children: o.label })] }, o.value))) }) }) }) })] }) }));
53
+ }
54
+ function optionList(children) {
55
+ return React.Children.toArray(children).flatMap((child) => {
56
+ if (!React.isValidElement(child))
57
+ return [];
58
+ const p = child.props;
59
+ if (child.type === React.Fragment ||
60
+ child.type === "optgroup" ||
61
+ child.type === NativeSelectOptGroup)
62
+ return optionList(p.children);
63
+ return [
64
+ {
65
+ value: String(p.value ?? p.children ?? ""),
66
+ label: p.label ?? p.children,
67
+ disabled: p.disabled,
68
+ },
69
+ ];
70
+ });
71
+ }
72
+ /** Transitional HTML-option adapter. Submitted values use Base UI's native hidden input. */
73
+ export const NativeSelect = React.forwardRef(function NativeSelect({ children, onChange, value, defaultValue, className, size, ...props }, ref) {
74
+ const input = React.useRef(null);
75
+ const options = optionList(children);
76
+ return (_jsx(Select, { options: options, value: value === undefined ? undefined : String(value), defaultValue: defaultValue === undefined ? undefined : String(defaultValue), className: className, size: size, inputRef: input, triggerRef: ref, name: props.name, form: props.form, disabled: props.disabled, required: props.required, id: props.id, "aria-label": props["aria-label"], "aria-labelledby": props["aria-labelledby"], "aria-describedby": props["aria-describedby"], "aria-invalid": props["aria-invalid"], onValueChange: (next) => {
77
+ if (!onChange)
78
+ return;
79
+ const target = input.current;
80
+ if (!target)
81
+ return;
82
+ const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
83
+ setter?.call(target, next);
84
+ const event = new Event("change", { bubbles: true });
85
+ Object.defineProperties(event, {
86
+ target: { value: target },
87
+ currentTarget: { value: target },
88
+ });
89
+ onChange(event);
90
+ } }));
91
+ });
92
+ export function NativeSelectOption(props) {
93
+ return _jsx("option", { ...props });
94
+ }
95
+ export function NativeSelectOptGroup(props) {
96
+ return _jsx("optgroup", { ...props });
97
+ }
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ export type ComponentSize = "small" | "default" | "large";
3
+ export type ComponentSizeAlias = ComponentSize | "sm" | "md" | "lg";
4
+ export declare const ComponentSizeContext: React.Context<ComponentSize | undefined>;
5
+ export declare function normalizeSize(size?: ComponentSizeAlias): ComponentSize | undefined;
6
+ /** Explicit sizes override the surrounding scheme; portals retain React context. */
7
+ export declare function useComponentSize(size?: ComponentSizeAlias): ComponentSize | undefined;
8
+ /** No layout box: grid/flex items and semantic children retain their own layout.
9
+ * For table internals, place the provider outside the table, never inside tbody. */
10
+ export declare function SizeProvider({ size, children, }: {
11
+ size: ComponentSize;
12
+ children: React.ReactNode;
13
+ }): React.JSX.Element;
package/dist/sizing.js ADDED
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ export const ComponentSizeContext = React.createContext(undefined);
5
+ export function normalizeSize(size) {
6
+ return size === "sm"
7
+ ? "small"
8
+ : size === "md"
9
+ ? "default"
10
+ : size === "lg"
11
+ ? "large"
12
+ : size;
13
+ }
14
+ /** Explicit sizes override the surrounding scheme; portals retain React context. */
15
+ export function useComponentSize(size) {
16
+ const inherited = React.useContext(ComponentSizeContext);
17
+ return normalizeSize(size) ?? inherited;
18
+ }
19
+ /** No layout box: grid/flex items and semantic children retain their own layout.
20
+ * For table internals, place the provider outside the table, never inside tbody. */
21
+ export function SizeProvider({ size, children, }) {
22
+ return (_jsx(ComponentSizeContext.Provider, { value: size, children: _jsx("div", { className: "pui-size-scope", "data-ui-size": size, children: children }) }));
23
+ }
package/dist/slot.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import * as React from "react";
2
+ export declare function Slot({ children, ...props }: React.HTMLAttributes<HTMLElement>): React.ReactElement<unknown, string | React.JSXElementConstructor<any>>;
package/dist/slot.js ADDED
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { useRender } from "@base-ui/react/use-render";
4
+ export function Slot({ children, ...props }) {
5
+ return useRender({
6
+ render: React.isValidElement(children) ? children : undefined,
7
+ props: React.isValidElement(children) ? props : { ...props, children },
8
+ defaultTagName: "span",
9
+ });
10
+ }
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ import { type ComponentSize } from "./sizing.js";
3
+ export type StatusTone = "neutral" | "success" | "danger" | "caution" | "progress";
4
+ export type BadgeProps = React.ComponentPropsWithoutRef<"span"> & {
5
+ size?: ComponentSize;
6
+ tone?: StatusTone;
7
+ icon?: React.ReactNode;
8
+ };
9
+ export declare const Badge: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
10
+ size?: ComponentSize;
11
+ tone?: StatusTone;
12
+ icon?: React.ReactNode;
13
+ } & React.RefAttributes<HTMLSpanElement>>;
14
+ export declare function StatusIcon({ tone }: {
15
+ tone: StatusTone;
16
+ }): React.JSX.Element;
17
+ /** Domain status labels and live announcements remain the caller's responsibility. */
18
+ export declare const StatusBadge: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
19
+ size?: ComponentSize;
20
+ tone?: StatusTone;
21
+ icon?: React.ReactNode;
22
+ } & React.RefAttributes<HTMLSpanElement>>;
package/dist/status.js ADDED
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useComponentSize } from "./sizing.js";
5
+ import { useToneTransition } from "./tone-transition.js";
6
+ export const Badge = React.forwardRef(function Badge({ tone = "neutral", icon, children, className, size, ...props }, ref) {
7
+ const scheme = useComponentSize(size);
8
+ const elementRef = useToneTransition(tone, ref);
9
+ return (_jsxs("span", { ...props, ref: elementRef, className: ["pui-badge", className].filter(Boolean).join(" "), "data-tone": tone, "data-ui-size": scheme, children: [icon && (_jsx("span", { className: "pui-badge-icon", "aria-hidden": "true", children: icon })), children] }));
10
+ });
11
+ export function StatusIcon({ tone }) {
12
+ return (_jsx("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.7", "aria-hidden": "true", children: tone === "success" ? (_jsx("path", { d: "m5 12 4 4L19 6" })) : tone === "caution" ? (_jsxs(_Fragment, { children: [_jsx("path", { d: "m12 3 10 18H2L12 3Z" }), _jsx("path", { d: "M12 9v4m0 3v1" })] })) : tone === "danger" ? (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 7v6m0 3v1" })] })) : tone === "progress" ? (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 7v6l4 2" })] })) : (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M8 12h8" })] })) }));
13
+ }
14
+ /** Domain status labels and live announcements remain the caller's responsibility. */
15
+ export const StatusBadge = React.forwardRef(function StatusBadge({ tone = "neutral", icon, ...props }, ref) {
16
+ return (_jsx(Badge, { ...props, ref: ref, tone: tone, icon: icon === undefined ? _jsx(StatusIcon, { tone: tone }) : icon }));
17
+ });
@@ -0,0 +1,5 @@
1
+ export type SyntaxToken = {
2
+ content: string;
3
+ role: string;
4
+ };
5
+ export declare function highlightCode(code: string, language: string): Promise<SyntaxToken[][] | null>;
@@ -0,0 +1,129 @@
1
+ import { createHighlighterCore } from "shiki/core";
2
+ import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
3
+ const loaders = {
4
+ tsx: () => import("shiki/langs/tsx.mjs"),
5
+ jsx: () => import("shiki/langs/jsx.mjs"),
6
+ typescript: () => import("shiki/langs/typescript.mjs"),
7
+ javascript: () => import("shiki/langs/javascript.mjs"),
8
+ css: () => import("shiki/langs/css.mjs"),
9
+ html: () => import("shiki/langs/html.mjs"),
10
+ json: () => import("shiki/langs/json.mjs"),
11
+ bash: () => import("shiki/langs/bash.mjs"),
12
+ yaml: () => import("shiki/langs/yaml.mjs"),
13
+ python: () => import("shiki/langs/python.mjs"),
14
+ sql: () => import("shiki/langs/sql.mjs"),
15
+ markdown: () => import("shiki/langs/markdown.mjs"),
16
+ };
17
+ const roles = [
18
+ "plain",
19
+ "comment",
20
+ "keyword",
21
+ "string",
22
+ "number",
23
+ "function",
24
+ "tag",
25
+ "attribute",
26
+ "punctuation",
27
+ ];
28
+ const colors = Object.fromEntries(roles.map((role, i) => [`#00000${i + 1}`, role]));
29
+ const color = (index) => `#00000${index + 1}`;
30
+ let engine;
31
+ const languages = new Map();
32
+ function highlighter() {
33
+ return (engine ??= createHighlighterCore({
34
+ engine: createJavaScriptRegexEngine({ forgiving: true }),
35
+ langs: [],
36
+ themes: [
37
+ {
38
+ name: "platform-quiet",
39
+ type: "dark",
40
+ colors: {
41
+ "editor.foreground": color(0),
42
+ "editor.background": "#000000",
43
+ },
44
+ tokenColors: [
45
+ {
46
+ scope: ["comment", "punctuation.definition.comment"],
47
+ settings: { foreground: color(1) },
48
+ },
49
+ {
50
+ scope: ["keyword", "storage", "constant.language"],
51
+ settings: { foreground: color(2) },
52
+ },
53
+ {
54
+ scope: ["string", "markup.inline.raw", "markup.fenced_code.block"],
55
+ settings: { foreground: color(3) },
56
+ },
57
+ {
58
+ scope: ["constant.numeric", "constant.character"],
59
+ settings: { foreground: color(4) },
60
+ },
61
+ {
62
+ scope: [
63
+ "entity.name.function",
64
+ "support.function",
65
+ "entity.name.type",
66
+ "support.type",
67
+ "entity.name.class",
68
+ ],
69
+ settings: { foreground: color(5) },
70
+ },
71
+ {
72
+ scope: ["entity.name.tag", "markup.heading"],
73
+ settings: { foreground: color(6) },
74
+ },
75
+ {
76
+ scope: [
77
+ "entity.other.attribute-name",
78
+ "support.type.property-name",
79
+ "variable.other.property",
80
+ ],
81
+ settings: { foreground: color(7) },
82
+ },
83
+ {
84
+ scope: ["punctuation", "meta.brace"],
85
+ settings: { foreground: color(8) },
86
+ },
87
+ ],
88
+ },
89
+ ],
90
+ }).catch((error) => {
91
+ engine = undefined;
92
+ throw error;
93
+ }));
94
+ }
95
+ /** Engine and grammars are singletons. Result cache is bounded by count and source length. */
96
+ const cache = new Map();
97
+ export async function highlightCode(code, language) {
98
+ const loader = loaders[language];
99
+ if (!loader || code.length > 50000)
100
+ return null;
101
+ const key = `${language}\0${code}`;
102
+ if (cache.has(key)) {
103
+ const value = cache.get(key);
104
+ cache.delete(key);
105
+ cache.set(key, value);
106
+ return value;
107
+ }
108
+ const instance = await highlighter();
109
+ if (!languages.has(language))
110
+ languages.set(language, loader()
111
+ .then((module) => instance.loadLanguage(module.default))
112
+ .catch((error) => {
113
+ languages.delete(language);
114
+ throw error;
115
+ }));
116
+ await languages.get(language);
117
+ const tokens = instance
118
+ .codeToTokensBase(code, { lang: language, theme: "platform-quiet" })
119
+ .map((line) => line.map((token) => ({
120
+ content: token.content,
121
+ role: colors[token.color?.toLowerCase() ?? ""] ?? "plain",
122
+ })));
123
+ if (code.length <= 8000) {
124
+ cache.set(key, tokens);
125
+ if (cache.size > 24)
126
+ cache.delete(cache.keys().next().value);
127
+ }
128
+ return tokens;
129
+ }
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ export declare const syntaxLanguages: readonly ["tsx", "jsx", "typescript", "javascript", "css", "html", "json", "bash", "yaml", "python", "sql", "markdown", "text"];
3
+ export type SyntaxHighlighterProps = Omit<React.ComponentPropsWithoutRef<"code">, "children"> & {
4
+ code: string;
5
+ language?: string;
6
+ inline?: boolean;
7
+ label?: string;
8
+ wrap?: boolean;
9
+ };
10
+ /** Rendering remains selectable React text; it never inserts executable HTML. */
11
+ export declare const SyntaxHighlighter: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "ref">, "children"> & {
12
+ code: string;
13
+ language?: string;
14
+ inline?: boolean;
15
+ label?: string;
16
+ wrap?: boolean;
17
+ } & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { ScrollArea } from "./scroll-area.js";
5
+ export const syntaxLanguages = [
6
+ "tsx",
7
+ "jsx",
8
+ "typescript",
9
+ "javascript",
10
+ "css",
11
+ "html",
12
+ "json",
13
+ "bash",
14
+ "yaml",
15
+ "python",
16
+ "sql",
17
+ "markdown",
18
+ "text",
19
+ ];
20
+ const aliases = {
21
+ ts: "typescript",
22
+ js: "javascript",
23
+ sh: "bash",
24
+ shell: "bash",
25
+ yml: "yaml",
26
+ py: "python",
27
+ md: "markdown",
28
+ xml: "html",
29
+ plaintext: "text",
30
+ txt: "text",
31
+ };
32
+ /** Rendering remains selectable React text; it never inserts executable HTML. */
33
+ export const SyntaxHighlighter = React.forwardRef(function SyntaxHighlighter({ code, language = "text", inline = false, label = "Highlighted source", wrap = false, className, ...props }, ref) {
34
+ const name = language.trim().toLowerCase();
35
+ const lang = aliases[name] ?? name;
36
+ const [result, setResult] = React.useState();
37
+ React.useEffect(() => {
38
+ let current = true;
39
+ if (lang === "text" ||
40
+ !syntaxLanguages.includes(lang) ||
41
+ code.length > 50000)
42
+ return;
43
+ import("./syntax-engine.js")
44
+ .then((engine) => engine.highlightCode(code, lang))
45
+ .then((tokens) => {
46
+ if (current)
47
+ setResult({ code, language: lang, tokens });
48
+ })
49
+ .catch(() => {
50
+ if (current)
51
+ setResult({ code, language: lang, tokens: null });
52
+ });
53
+ return () => {
54
+ current = false;
55
+ };
56
+ }, [code, lang]);
57
+ const tokens = result?.code === code && result.language === lang ? result.tokens : null;
58
+ const eols = code.match(/\r\n|\n|\r/g) ?? [];
59
+ const valid = tokens && tokens.length === eols.length + 1;
60
+ const element = (_jsx("code", { ...props, ref: ref, className: ["pui-syntax", className].filter(Boolean).join(" "), "data-language": lang, "data-highlighted": valid ? "true" : "false", children: valid
61
+ ? tokens.map((line, i) => (_jsxs(React.Fragment, { children: [line.map((token, j) => (_jsx("span", { "data-syntax": token.role, children: token.content }, j))), eols[i] ?? ""] }, i)))
62
+ : code }));
63
+ return inline ? (element) : (_jsx(ScrollArea, { label: label, orientation: "horizontal", children: _jsx("pre", { className: "pui-syntax-pre", "data-wrap": wrap || undefined, children: element }) }));
64
+ });
@@ -0,0 +1,59 @@
1
+ import * as React from "react";
2
+ import { Toast as BaseToast } from "@base-ui/react/toast";
3
+ import { type StatusTone } from "./status.js";
4
+ export type ToastRootProps = React.ComponentPropsWithoutRef<typeof BaseToast.Root> & {
5
+ tone?: StatusTone;
6
+ };
7
+ export type ToastViewportProps = React.ComponentPropsWithoutRef<typeof BaseToast.Viewport> & {
8
+ layout?: "stack" | "list" | "anchored";
9
+ };
10
+ export declare const Toast: {
11
+ Root: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastRootProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & {
12
+ tone?: StatusTone;
13
+ } & React.RefAttributes<HTMLDivElement>>;
14
+ Icon: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
15
+ tone?: StatusTone;
16
+ } & React.RefAttributes<HTMLSpanElement>>;
17
+ Close: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
18
+ Action: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastActionProps, "ref"> & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
19
+ Viewport: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastViewportProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & {
20
+ layout?: "stack" | "list" | "anchored";
21
+ } & React.RefAttributes<HTMLDivElement>>;
22
+ Content: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastContentProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
23
+ size?: import("./sizing.js").ComponentSize;
24
+ } & React.RefAttributes<HTMLDivElement>>;
25
+ Title: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>, "ref">, "size"> & {
26
+ size?: import("./sizing.js").ComponentSize;
27
+ } & React.RefAttributes<HTMLHeadingElement>>;
28
+ Description: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>, "ref">, "size"> & {
29
+ size?: import("./sizing.js").ComponentSize;
30
+ } & React.RefAttributes<HTMLParagraphElement>>;
31
+ Text: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "size"> & {
32
+ size?: import("./sizing.js").ComponentSize;
33
+ } & React.RefAttributes<HTMLDivElement>>;
34
+ Positioner: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastPositionerProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
35
+ size?: import("./sizing.js").ComponentSize;
36
+ } & React.RefAttributes<HTMLDivElement>>;
37
+ Provider: React.FC<import("@base-ui/react/toast").ToastProviderProps>;
38
+ Portal: React.ForwardRefExoticComponent<Omit<import("@base-ui/react/toast").ToastPortalProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
39
+ Arrow: React.ForwardRefExoticComponent<Omit<import("@base-ui/react/toast").ToastArrowProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
40
+ useToastManager: typeof BaseToast.useToastManager;
41
+ createToastManager: typeof BaseToast.createToastManager;
42
+ };
43
+ export declare const ToastAction: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastActionProps, "ref"> & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>, ToastArrow: React.ForwardRefExoticComponent<Omit<import("@base-ui/react/toast").ToastArrowProps, "ref"> & React.RefAttributes<HTMLDivElement>>, ToastClose: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>, ToastContent: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastContentProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
44
+ size?: import("./sizing.js").ComponentSize;
45
+ } & React.RefAttributes<HTMLDivElement>>, ToastDescription: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>, "ref">, "size"> & {
46
+ size?: import("./sizing.js").ComponentSize;
47
+ } & React.RefAttributes<HTMLParagraphElement>>, ToastPortal: React.ForwardRefExoticComponent<Omit<import("@base-ui/react/toast").ToastPortalProps, "ref"> & React.RefAttributes<HTMLDivElement>>, ToastPositioner: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastPositionerProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
48
+ size?: import("./sizing.js").ComponentSize;
49
+ } & React.RefAttributes<HTMLDivElement>>, ToastProvider: React.FC<import("@base-ui/react/toast").ToastProviderProps>, ToastRoot: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastRootProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & {
50
+ tone?: StatusTone;
51
+ } & React.RefAttributes<HTMLDivElement>>, ToastTitle: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/toast").ToastTitleProps, "ref"> & React.RefAttributes<HTMLHeadingElement>, "ref">, "size"> & {
52
+ size?: import("./sizing.js").ComponentSize;
53
+ } & React.RefAttributes<HTMLHeadingElement>>, ToastViewport: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/toast").ToastViewportProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & {
54
+ layout?: "stack" | "list" | "anchored";
55
+ } & React.RefAttributes<HTMLDivElement>>, ToastIcon: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
56
+ tone?: StatusTone;
57
+ } & React.RefAttributes<HTMLSpanElement>>, ToastText: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "size"> & {
58
+ size?: import("./sizing.js").ComponentSize;
59
+ } & React.RefAttributes<HTMLDivElement>>;
package/dist/toast.js ADDED
@@ -0,0 +1,99 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useComponentSize } from "./sizing.js";
5
+ import { Toast as BaseToast } from "@base-ui/react/toast";
6
+ import { part, skin } from "./parts.js";
7
+ import { StatusIcon } from "./status.js";
8
+ const ToneContext = React.createContext("neutral");
9
+ const Root = React.forwardRef(function Root({ tone = "neutral", className, ...props }, ref) {
10
+ return (_jsx(ToneContext.Provider, { value: tone, children: _jsx(BaseToast.Root, { ...props, ref: ref, "data-tone": tone, className: skin("pui-toast", className) }) }));
11
+ });
12
+ const Icon = React.forwardRef(function Icon({ tone, children, className, ...props }, ref) {
13
+ const inherited = React.useContext(ToneContext);
14
+ return (_jsx("span", { ...props, ref: ref, "aria-hidden": "true", className: ["pui-toast-icon", className].filter(Boolean).join(" "), children: children ?? _jsx(StatusIcon, { tone: tone ?? inherited }) }));
15
+ });
16
+ const Close = React.forwardRef(function Close({ className, children, ...props }, ref) {
17
+ return (_jsx(BaseToast.Close, { "aria-label": "Dismiss notification", "aria-hidden": false, "data-variant": "ghost", "data-size": "icon", ...props, ref: ref, className: skin("pui-button pui-toast-close", className), children: children ?? (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", "aria-hidden": "true", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) })) }));
18
+ });
19
+ const Action = React.forwardRef(function Action({ className, ...props }, ref) {
20
+ return (_jsx(BaseToast.Action, { "data-variant": "outline", "data-size": "sm", ...props, ref: ref, className: skin("pui-button pui-toast-action", className) }));
21
+ });
22
+ const Viewport = React.forwardRef(function Viewport({ layout = "stack", className, ...props }, ref) {
23
+ const viewportRef = React.useRef(null);
24
+ React.useLayoutEffect(() => {
25
+ const viewport = viewportRef.current;
26
+ if (!viewport || layout !== "stack")
27
+ return;
28
+ const resize = new ResizeObserver(update);
29
+ function update() {
30
+ const cards = Array.from(viewport.querySelectorAll(".pui-toast:not([data-limited])"));
31
+ const gap = parseFloat(getComputedStyle(viewport).rowGap) || 0;
32
+ const height = cards.reduce((sum, card) => {
33
+ const natural = parseFloat(getComputedStyle(card).getPropertyValue("--toast-height"));
34
+ return (sum + (Number.isFinite(natural) ? natural : card.offsetHeight));
35
+ }, 0) +
36
+ Math.max(0, cards.length - 1) * gap;
37
+ const wasOverflow = viewport.hasAttribute("data-stack-overflow");
38
+ const pinned = viewport.scrollTop >=
39
+ viewport.scrollHeight - viewport.clientHeight - 8;
40
+ const overflowing = height + 48 > window.innerHeight;
41
+ viewport.toggleAttribute("data-stack-overflow", overflowing);
42
+ const value = `${height}px`;
43
+ if (viewport.style.getPropertyValue("--toast-stack-height") !== value)
44
+ viewport.style.setProperty("--toast-stack-height", value);
45
+ if (overflowing && (!wasOverflow || pinned))
46
+ viewport.scrollTop = viewport.scrollHeight;
47
+ }
48
+ function refresh() {
49
+ resize.disconnect();
50
+ viewport
51
+ .querySelectorAll(".pui-toast:not([data-limited])")
52
+ .forEach((card) => resize.observe(card));
53
+ update();
54
+ }
55
+ const mutation = new MutationObserver((records) => {
56
+ // Natural-height variables are on roots; ignore our own viewport sizing writes.
57
+ if (records.some((record) => record.target !== viewport ||
58
+ record.attributeName === "data-expanded"))
59
+ refresh();
60
+ });
61
+ mutation.observe(viewport, {
62
+ childList: true,
63
+ subtree: true,
64
+ attributes: true,
65
+ attributeFilter: ["data-limited", "data-expanded", "style"],
66
+ });
67
+ refresh();
68
+ window.addEventListener("resize", update);
69
+ return () => {
70
+ window.removeEventListener("resize", update);
71
+ viewport.removeAttribute("data-stack-overflow");
72
+ resize.disconnect();
73
+ mutation.disconnect();
74
+ };
75
+ }, [layout]);
76
+ const scheme = useComponentSize();
77
+ return (_jsx(BaseToast.Viewport, { ...props, ref: (element) => {
78
+ viewportRef.current = element;
79
+ if (typeof ref === "function")
80
+ ref(element);
81
+ else if (ref)
82
+ ref.current = element;
83
+ }, "data-layout": layout, "data-ui-size": scheme, className: skin("pui-toast-viewport", className) }));
84
+ });
85
+ export const Toast = {
86
+ ...BaseToast,
87
+ Root,
88
+ Icon,
89
+ Close,
90
+ Action,
91
+ Viewport,
92
+ Content: part(BaseToast.Content, "pui-toast-content"),
93
+ Title: part(BaseToast.Title, "pui-toast-title"),
94
+ Description: part(BaseToast.Description, "pui-toast-description"),
95
+ Text: part("div", "pui-toast-text"),
96
+ Positioner: part(BaseToast.Positioner, "pui-positioner"),
97
+ };
98
+ // Flat names for React Server Components, which cannot dot into client exports.
99
+ export const { Action: ToastAction, Arrow: ToastArrow, Close: ToastClose, Content: ToastContent, Description: ToastDescription, Portal: ToastPortal, Positioner: ToastPositioner, Provider: ToastProvider, Root: ToastRoot, Title: ToastTitle, Viewport: ToastViewport, Icon: ToastIcon, Text: ToastText, } = Toast;
@@ -0,0 +1,4 @@
1
+ import * as React from "react";
2
+ /** Animate a scalar so the color-mix expression, not a browser's default
3
+ * color interpolation space, determines every intermediate status color. */
4
+ export declare function useToneTransition<Element extends HTMLElement>(tone: string, forwardedRef: React.ForwardedRef<Element>): React.RefObject<Element | null>;