@bettercms-ai/kit 2.0.0 → 2.0.2
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/dist/adapters/lossy.d.ts +14 -6
- package/dist/adapters/lossy.js +23 -11
- package/dist/adapters/lossy.js.map +1 -1
- package/dist/adapters/prod-types.d.ts +46 -14
- package/dist/adapters/prod-types.js +37 -35
- package/dist/adapters/prod-types.js.map +1 -1
- package/dist/adapters/schema.d.ts +39 -0
- package/dist/adapters/schema.js +281 -82
- package/dist/adapters/schema.js.map +1 -1
- package/dist/context.d.ts +39 -0
- package/dist/context.js +40 -1
- package/dist/context.js.map +1 -1
- package/dist/controls/form-picker-register.d.ts +7 -1
- package/dist/controls/form-picker-register.js +1 -5
- package/dist/controls/form-picker-register.js.map +1 -1
- package/dist/controls/icon.d.ts +1 -1
- package/dist/controls/icon.js +1 -1
- package/dist/controls/icon.js.map +1 -1
- package/dist/controls/link.d.ts +11 -4
- package/dist/controls/link.js +120 -202
- package/dist/controls/link.js.map +1 -1
- package/dist/controls/location.js +3 -8
- package/dist/controls/location.js.map +1 -1
- package/dist/controls/media.d.ts +0 -14
- package/dist/controls/media.js +52 -95
- package/dist/controls/media.js.map +1 -1
- package/dist/controls/reference-services.d.ts +23 -2
- package/dist/controls/reference.d.ts +2 -2
- package/dist/controls/reference.js +79 -28
- package/dist/controls/reference.js.map +1 -1
- package/dist/controls/rich-text/article-shell.d.ts +145 -0
- package/dist/controls/rich-text/article-shell.js +514 -0
- package/dist/controls/rich-text/article-shell.js.map +1 -0
- package/dist/controls/rich-text/author.d.ts +25 -0
- package/dist/controls/rich-text/author.js +107 -0
- package/dist/controls/rich-text/author.js.map +1 -0
- package/dist/controls/rich-text/blocks.d.ts +19 -2
- package/dist/controls/rich-text/blocks.js +275 -638
- package/dist/controls/rich-text/blocks.js.map +1 -1
- package/dist/controls/rich-text/button.d.ts +23 -0
- package/dist/controls/rich-text/button.js +81 -0
- package/dist/controls/rich-text/button.js.map +1 -0
- package/dist/controls/rich-text/catalog.d.ts +2 -5
- package/dist/controls/rich-text/catalog.js +121 -7
- package/dist/controls/rich-text/catalog.js.map +1 -1
- package/dist/controls/rich-text/checklist.d.ts +24 -0
- package/dist/controls/rich-text/checklist.js +115 -0
- package/dist/controls/rich-text/checklist.js.map +1 -0
- package/dist/controls/rich-text/codec.d.ts +15 -1
- package/dist/controls/rich-text/codec.js +11 -1
- package/dist/controls/rich-text/codec.js.map +1 -1
- package/dist/controls/rich-text/columns.d.ts +41 -0
- package/dist/controls/rich-text/columns.js +181 -0
- package/dist/controls/rich-text/columns.js.map +1 -0
- package/dist/controls/rich-text/control.js +1 -0
- package/dist/controls/rich-text/control.js.map +1 -1
- package/dist/controls/rich-text/dom.d.ts +84 -4
- package/dist/controls/rich-text/dom.js +171 -10
- package/dist/controls/rich-text/dom.js.map +1 -1
- package/dist/controls/rich-text/editor.d.ts +4 -2
- package/dist/controls/rich-text/editor.js +199 -26
- package/dist/controls/rich-text/editor.js.map +1 -1
- package/dist/controls/rich-text/embed.d.ts +28 -0
- package/dist/controls/rich-text/embed.js +210 -0
- package/dist/controls/rich-text/embed.js.map +1 -0
- package/dist/controls/rich-text/footnote.d.ts +41 -0
- package/dist/controls/rich-text/footnote.js +185 -0
- package/dist/controls/rich-text/footnote.js.map +1 -0
- package/dist/controls/rich-text/gallery.d.ts +28 -0
- package/dist/controls/rich-text/gallery.js +167 -0
- package/dist/controls/rich-text/gallery.js.map +1 -0
- package/dist/controls/rich-text/link-form.d.ts +13 -1
- package/dist/controls/rich-text/link-form.js +41 -7
- package/dist/controls/rich-text/link-form.js.map +1 -1
- package/dist/controls/rich-text/ops.js +6 -0
- package/dist/controls/rich-text/ops.js.map +1 -1
- package/dist/controls/rich-text/related.d.ts +49 -0
- package/dist/controls/rich-text/related.js +233 -0
- package/dist/controls/rich-text/related.js.map +1 -0
- package/dist/controls/rich-text/toc.d.ts +47 -0
- package/dist/controls/rich-text/toc.js +160 -0
- package/dist/controls/rich-text/toc.js.map +1 -0
- package/dist/controls/slot-register.d.ts +6 -1
- package/dist/controls/slot-register.js +1 -4
- package/dist/controls/slot-register.js.map +1 -1
- package/dist/core/core.d.ts +11 -3
- package/dist/core/core.js +24 -6
- package/dist/core/core.js.map +1 -1
- package/dist/core/facets.d.ts +16 -0
- package/dist/core/facets.js +17 -1
- package/dist/core/facets.js.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/ledgers/exclusions.v1.js +141 -0
- package/dist/ledgers/exclusions.v1.js.map +1 -0
- package/dist/library/coverage.d.ts +13 -1
- package/dist/library/coverage.js +12 -1
- package/dist/library/coverage.js.map +1 -1
- package/dist/library/kit-catalog.json +158 -154
- package/dist/schema/types.d.ts +17 -0
- package/dist/schema/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing/coverage-extras.d.ts +8 -0
- package/dist/testing/coverage-extras.js +44 -0
- package/dist/testing/coverage-extras.js.map +1 -0
- package/dist/testing/exclusions.d.ts +30 -0
- package/dist/testing/exclusions.js +13 -0
- package/dist/testing/exclusions.js.map +1 -0
- package/dist/ui/autocomplete.d.ts +29 -1
- package/dist/ui/autocomplete.js +102 -47
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/button.js +3 -5
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/index.js +2 -2
- package/dist/ui/overlay.d.ts +4 -1
- package/dist/ui/overlay.js +2 -1
- package/dist/ui/overlay.js.map +1 -1
- package/dist/ui/toast.js +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage extras: kit-own entries for what V2's corpus never exercises, kept OUT of the corpus (which stays V2
|
|
3
|
+
* verbatim: catalog-copy.test). coverageOf reports them as their own row, so the type count reads "28/29 from V2,
|
|
4
|
+
* json via extras" and never passes off a kit fixture as V2's. Each extra runs the same gates as a corpus entry.
|
|
5
|
+
*/
|
|
6
|
+
import type { CatalogItem } from "../library/catalog";
|
|
7
|
+
/** V2's corpus has no `json` field: one entry holds one, with a nested value, so the adapters and the dock carry it. */
|
|
8
|
+
export declare const COVERAGE_EXTRAS: CatalogItem[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/testing/coverage-extras.ts
|
|
2
|
+
/** V2's corpus has no `json` field: one entry holds one, with a nested value, so the adapters and the dock carry it. */
|
|
3
|
+
var COVERAGE_EXTRAS = [{
|
|
4
|
+
id: "extra-json-field",
|
|
5
|
+
core: false,
|
|
6
|
+
category: "Advanced patterns",
|
|
7
|
+
tags: [],
|
|
8
|
+
hay: "json value extra",
|
|
9
|
+
ex: {
|
|
10
|
+
id: "extra-json-field",
|
|
11
|
+
label: "Raw JSON value",
|
|
12
|
+
proves: "A json field (the one type V2's corpus never uses) is carried by both lanes and edited in the dock",
|
|
13
|
+
schema: {
|
|
14
|
+
name: "jsonSettings",
|
|
15
|
+
title: "JSON settings",
|
|
16
|
+
kind: "object",
|
|
17
|
+
fields: [{
|
|
18
|
+
name: "title",
|
|
19
|
+
title: "Title",
|
|
20
|
+
type: "string"
|
|
21
|
+
}, {
|
|
22
|
+
name: "settings",
|
|
23
|
+
title: "Settings",
|
|
24
|
+
type: "json",
|
|
25
|
+
description: "Raw settings the site reads as they are."
|
|
26
|
+
}]
|
|
27
|
+
},
|
|
28
|
+
value: {
|
|
29
|
+
title: "Map widget",
|
|
30
|
+
settings: {
|
|
31
|
+
zoom: 12,
|
|
32
|
+
center: [52.37, 4.9],
|
|
33
|
+
layers: ["streets", {
|
|
34
|
+
id: "pins",
|
|
35
|
+
visible: true
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}];
|
|
41
|
+
//#endregion
|
|
42
|
+
export { COVERAGE_EXTRAS };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=coverage-extras.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage-extras.js","names":[],"sources":["../../src/testing/coverage-extras.ts"],"sourcesContent":["/**\n * Coverage extras: kit-own entries for what V2's corpus never exercises, kept OUT of the corpus (which stays V2\n * verbatim: catalog-copy.test). coverageOf reports them as their own row, so the type count reads \"28/29 from V2,\n * json via extras\" and never passes off a kit fixture as V2's. Each extra runs the same gates as a corpus entry.\n */\nimport type { CatalogItem } from \"../library/catalog\";\n\n/** V2's corpus has no `json` field: one entry holds one, with a nested value, so the adapters and the dock carry it. */\nexport const COVERAGE_EXTRAS: CatalogItem[] = [\n {\n id: \"extra-json-field\",\n core: false,\n category: \"Advanced patterns\",\n tags: [],\n hay: \"json value extra\",\n ex: {\n id: \"extra-json-field\",\n label: \"Raw JSON value\",\n proves: \"A json field (the one type V2's corpus never uses) is carried by both lanes and edited in the dock\",\n schema: {\n name: \"jsonSettings\",\n title: \"JSON settings\",\n kind: \"object\",\n fields: [\n { name: \"title\", title: \"Title\", type: \"string\" },\n { name: \"settings\", title: \"Settings\", type: \"json\", description: \"Raw settings the site reads as they are.\" },\n ],\n },\n value: { title: \"Map widget\", settings: { zoom: 12, center: [52.37, 4.9], layers: [\"streets\", { id: \"pins\", visible: true }] } },\n },\n },\n];\n"],"mappings":";;AAQA,IAAa,kBAAiC,CAC5C;CACE,IAAI;CACJ,MAAM;CACN,UAAU;CACV,MAAM,CAAC;CACP,KAAK;CACL,IAAI;EACF,IAAI;EACJ,OAAO;EACP,QAAQ;EACR,QAAQ;GACN,MAAM;GACN,OAAO;GACP,MAAM;GACN,QAAQ,CACN;IAAE,MAAM;IAAS,OAAO;IAAS,MAAM;GAAS,GAChD;IAAE,MAAM;IAAY,OAAO;IAAY,MAAM;IAAQ,aAAa;GAA2C,CAC/G;EACF;EACA,OAAO;GAAE,OAAO;GAAc,UAAU;IAAE,MAAM;IAAI,QAAQ,CAAC,OAAO,GAAG;IAAG,QAAQ,CAAC,WAAW;KAAE,IAAI;KAAQ,SAAS;IAAK,CAAC;GAAE;EAAE;CACjI;AACF,CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface KitExclusion {
|
|
2
|
+
id: string;
|
|
3
|
+
plane: "schema" | "value";
|
|
4
|
+
/** the lossy feature it signs (adapters/lossy.ts FEATURES), or a backend rule's code */
|
|
5
|
+
feature?: string;
|
|
6
|
+
key?: string;
|
|
7
|
+
/** only this part of the feature (e.g. the Shopify subset of reference targets) */
|
|
8
|
+
pattern?: string;
|
|
9
|
+
reason: string;
|
|
10
|
+
signedBy: string;
|
|
11
|
+
}
|
|
12
|
+
export interface KitEquivalence {
|
|
13
|
+
id: string;
|
|
14
|
+
/** value: a stored value moved; schema: a definition key whose effect another key carries (its `feature` is not loss) */
|
|
15
|
+
plane: "schema" | "value";
|
|
16
|
+
/** schema plane: the lossy feature (adapters/lossy.ts CATALOG) the move accounts for */
|
|
17
|
+
feature?: string;
|
|
18
|
+
key: string;
|
|
19
|
+
/** where prod holds the fact instead */
|
|
20
|
+
moves: string;
|
|
21
|
+
/** what the gate's proof checks to read it back */
|
|
22
|
+
recovery: string;
|
|
23
|
+
/** schema plane: the V2 source (or the gate) that shows the key has no effect of its own, or where it is carried */
|
|
24
|
+
proof?: string;
|
|
25
|
+
/** the write that carries the fact (a planner): the gates measure it rather than exclude the feature */
|
|
26
|
+
carriedBy?: string;
|
|
27
|
+
ruledBy?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const EXCLUSIONS: KitExclusion[];
|
|
30
|
+
export declare const EQUIVALENCES: KitEquivalence[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import exclusions_v1_default from "../ledgers/exclusions.v1.js";
|
|
2
|
+
//#region src/testing/exclusions.ts
|
|
3
|
+
/**
|
|
4
|
+
* The exclusions ledger (ledgers/exclusions.v1.json), for the gates that hold the V2 → prod mapping to it: a lost key
|
|
5
|
+
* is an owner-signed exclusion or a proven equivalence (a representation move), never anything else. The dashboard's
|
|
6
|
+
* kitCorpusDock gate reads it from here.
|
|
7
|
+
*/
|
|
8
|
+
var EXCLUSIONS = exclusions_v1_default.exclusions;
|
|
9
|
+
var EQUIVALENCES = exclusions_v1_default.equivalences;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { EQUIVALENCES, EXCLUSIONS };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=exclusions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exclusions.js","names":[],"sources":["../../src/testing/exclusions.ts"],"sourcesContent":["/**\n * The exclusions ledger (ledgers/exclusions.v1.json), for the gates that hold the V2 → prod mapping to it: a lost key\n * is an owner-signed exclusion or a proven equivalence (a representation move), never anything else. The dashboard's\n * kitCorpusDock gate reads it from here.\n */\nimport ledger from \"../../ledgers/exclusions.v1.json\";\n\nexport interface KitExclusion {\n id: string;\n plane: \"schema\" | \"value\";\n /** the lossy feature it signs (adapters/lossy.ts FEATURES), or a backend rule's code */\n feature?: string;\n key?: string;\n /** only this part of the feature (e.g. the Shopify subset of reference targets) */\n pattern?: string;\n reason: string;\n signedBy: string;\n}\n\nexport interface KitEquivalence {\n id: string;\n /** value: a stored value moved; schema: a definition key whose effect another key carries (its `feature` is not loss) */\n plane: \"schema\" | \"value\";\n /** schema plane: the lossy feature (adapters/lossy.ts CATALOG) the move accounts for */\n feature?: string;\n key: string;\n /** where prod holds the fact instead */\n moves: string;\n /** what the gate's proof checks to read it back */\n recovery: string;\n /** schema plane: the V2 source (or the gate) that shows the key has no effect of its own, or where it is carried */\n proof?: string;\n /** the write that carries the fact (a planner): the gates measure it rather than exclude the feature */\n carriedBy?: string;\n ruledBy?: string;\n}\n\nexport const EXCLUSIONS = ledger.exclusions as KitExclusion[];\nexport const EQUIVALENCES = ledger.equivalences as KitEquivalence[];\n"],"mappings":";;;;;;;AAqCA,IAAa,aAAa,sBAAO;AACjC,IAAa,eAAe,sBAAO"}
|
|
@@ -18,13 +18,41 @@ export interface AutocompleteProps {
|
|
|
18
18
|
filter?: ((options: SelectOption[], query: string) => SelectOption[]) | null;
|
|
19
19
|
placeholder?: string;
|
|
20
20
|
size?: "sm" | "md" | "lg";
|
|
21
|
+
/** decorative icon before the input */
|
|
21
22
|
leading?: React.ReactNode;
|
|
23
|
+
/** an interactive control inside the field before the input (the link-type menu); rendered as is */
|
|
24
|
+
leadingSlot?: React.ReactNode;
|
|
22
25
|
loading?: boolean;
|
|
23
26
|
invalid?: boolean;
|
|
24
27
|
disabled?: boolean;
|
|
25
28
|
/** open the suggestions when the field takes focus (default true, as V2) */
|
|
26
29
|
openOnFocus?: boolean;
|
|
30
|
+
/** controlled open; the list shows while open and it has rows or a note */
|
|
31
|
+
open?: boolean;
|
|
32
|
+
onOpenChange?: (open: boolean) => void;
|
|
33
|
+
/** render the list in the flow under the field instead of a floating popup (panels, popovers) */
|
|
34
|
+
inlineList?: boolean;
|
|
35
|
+
/** a status row shown, with the list kept open, when no row matches */
|
|
36
|
+
emptyNote?: React.ReactNode;
|
|
37
|
+
/** a status row under the rows when there are rows (a partial-load error) */
|
|
38
|
+
listNote?: React.ReactNode;
|
|
39
|
+
/** highlight row 0 on every keystroke that leaves text, so Enter picks it; empty text highlights nothing */
|
|
40
|
+
highlightFirst?: boolean;
|
|
41
|
+
/** emphasise the query inside row labels (default true; V2's link rows draw the plain title) */
|
|
42
|
+
highlightMatches?: boolean;
|
|
43
|
+
/** the listbox's accessible name */
|
|
44
|
+
listLabel?: string;
|
|
45
|
+
/** a pick also writes the row's label into the text (default true); false = a pick only calls onSelect */
|
|
46
|
+
fillOnSelect?: boolean;
|
|
47
|
+
/** replaces the built-in clear (setText("")) on the X */
|
|
48
|
+
onClear?: () => void;
|
|
49
|
+
/** a control inside the field after the clear button (the file-library button) */
|
|
50
|
+
trailingSlot?: React.ReactNode;
|
|
51
|
+
/** data-* attributes on the list's layer (the floating positioner, or the inline wrapper) */
|
|
52
|
+
listAttrs?: Record<`data-${string}`, string>;
|
|
53
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
54
|
+
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
27
55
|
className?: string;
|
|
28
56
|
menuClassName?: string;
|
|
29
57
|
}
|
|
30
|
-
export declare function Autocomplete({ id, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, value, defaultValue, onValueChange, options, onSelect, onEnter, filter, placeholder, size, leading, loading, invalid, disabled, openOnFocus, className, menuClassName, }: AutocompleteProps): import("react/jsx-runtime").JSX.Element;
|
|
58
|
+
export declare function Autocomplete({ id, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, value, defaultValue, onValueChange, options, onSelect, onEnter, filter, placeholder, size, leading, leadingSlot, loading, invalid, disabled, openOnFocus, open: openProp, onOpenChange, inlineList, emptyNote, listNote, highlightFirst, highlightMatches, listLabel, fillOnSelect, onClear, trailingSlot, listAttrs, inputRef, onBlur, className, menuClassName, }: AutocompleteProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/ui/autocomplete.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { useFieldDescribedBy, useFieldId, useFieldLabelledBy, useLabelFor } from "./field-context.js";
|
|
3
|
+
import { Spinner } from "./spinner.js";
|
|
3
4
|
import { KIT_SCOPE } from "./surfaces.js";
|
|
4
5
|
import { IconButton } from "./button.js";
|
|
5
6
|
import { Highlight, filterOptions, nodeText } from "./select.js";
|
|
6
|
-
import { Spinner } from "./spinner.js";
|
|
7
7
|
import * as React from "react";
|
|
8
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { IconX } from "@tabler/icons-react";
|
|
10
10
|
import { Autocomplete as Autocomplete$1 } from "@base-ui/react/autocomplete";
|
|
11
11
|
//#region src/ui/autocomplete.tsx
|
|
@@ -14,7 +14,8 @@ var H = {
|
|
|
14
14
|
md: "h-8",
|
|
15
15
|
lg: "h-9"
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
var NOTE = "px-2 py-1.5 text-kit-xs text-kit-fg-3";
|
|
18
|
+
function Autocomplete({ id, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, value, defaultValue = "", onValueChange, options, onSelect, onEnter, filter = filterOptions, placeholder, size = "md", leading, leadingSlot, loading, invalid, disabled, openOnFocus = true, open: openProp, onOpenChange, inlineList = false, emptyNote, listNote, highlightFirst = false, highlightMatches = true, listLabel, fillOnSelect = true, onClear, trailingSlot, listAttrs, inputRef, onBlur, className, menuClassName }) {
|
|
18
19
|
const fieldId = useFieldId();
|
|
19
20
|
const labelledBy = useFieldLabelledBy();
|
|
20
21
|
const labelFor = useLabelFor(id ?? fieldId);
|
|
@@ -25,36 +26,105 @@ function Autocomplete({ id, "aria-label": ariaLabel, "aria-describedby": ariaDes
|
|
|
25
26
|
if (value === void 0) setInner(t);
|
|
26
27
|
onValueChange?.(t);
|
|
27
28
|
};
|
|
28
|
-
const [
|
|
29
|
-
const
|
|
29
|
+
const [innerOpen, setInnerOpen] = React.useState(false);
|
|
30
|
+
const open = openProp ?? innerOpen;
|
|
31
|
+
const setOpen = (o) => {
|
|
32
|
+
if (openProp === void 0) setInnerOpen(o);
|
|
33
|
+
onOpenChange?.(o);
|
|
34
|
+
};
|
|
35
|
+
const [highlighted, setHighlighted] = React.useState(void 0);
|
|
30
36
|
const shown = React.useMemo(() => filter ? filter(options, text) : options, [
|
|
31
37
|
filter,
|
|
32
38
|
options,
|
|
33
39
|
text
|
|
34
40
|
]);
|
|
41
|
+
const hasEmptyNote = emptyNote != null && emptyNote !== false;
|
|
42
|
+
const visible = open && (shown.length > 0 || hasEmptyNote);
|
|
43
|
+
const firstStandsIn = highlightFirst && visible && text.trim() !== "" && !highlighted && shown.length > 0 && !shown[0].disabled;
|
|
44
|
+
const pick = (o) => {
|
|
45
|
+
onSelect?.(o);
|
|
46
|
+
if (fillOnSelect) setText(nodeText(o.label));
|
|
47
|
+
setOpen(false);
|
|
48
|
+
};
|
|
35
49
|
const named = id ?? fieldId ?? ariaLabel;
|
|
50
|
+
const rows = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
51
|
+
/* @__PURE__ */ jsx(Autocomplete$1.List, {
|
|
52
|
+
"aria-label": listLabel,
|
|
53
|
+
className: "max-h-72 overflow-y-auto overscroll-contain empty:hidden",
|
|
54
|
+
children: shown.map((o, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [o.group && (i === 0 || shown[i - 1].group !== o.group) ? /* @__PURE__ */ jsx("div", {
|
|
55
|
+
role: "presentation",
|
|
56
|
+
className: "px-2 pb-1 pt-2 text-kit-xs font-medium text-kit-fg-3 first:pt-1",
|
|
57
|
+
children: o.group
|
|
58
|
+
}) : null, /* @__PURE__ */ jsxs(Autocomplete$1.Item, {
|
|
59
|
+
value: o,
|
|
60
|
+
disabled: o.disabled,
|
|
61
|
+
onClick: () => onSelect?.(o),
|
|
62
|
+
"data-first-active": firstStandsIn && i === 0 ? "" : void 0,
|
|
63
|
+
className: "flex h-8 cursor-pointer select-none items-center gap-2 rounded-kit-md px-2 text-kit-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-40 data-[highlighted]:bg-kit-hover data-[first-active]:bg-kit-hover",
|
|
64
|
+
children: [
|
|
65
|
+
o.icon ? /* @__PURE__ */ jsx("span", {
|
|
66
|
+
"aria-hidden": true,
|
|
67
|
+
className: "flex shrink-0 text-kit-fg-3 [&_svg]:size-4",
|
|
68
|
+
children: o.icon
|
|
69
|
+
}) : null,
|
|
70
|
+
/* @__PURE__ */ jsx("span", {
|
|
71
|
+
className: "min-w-0 shrink truncate text-kit-fg",
|
|
72
|
+
children: highlightMatches && typeof o.label === "string" ? /* @__PURE__ */ jsx(Highlight, {
|
|
73
|
+
text: o.label,
|
|
74
|
+
query: text
|
|
75
|
+
}) : o.label
|
|
76
|
+
}),
|
|
77
|
+
o.description ? /* @__PURE__ */ jsx("span", {
|
|
78
|
+
className: "min-w-0 flex-1 truncate text-kit-xs text-kit-fg-3",
|
|
79
|
+
children: o.description
|
|
80
|
+
}) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
81
|
+
o.meta ? /* @__PURE__ */ jsx("span", {
|
|
82
|
+
className: "shrink-0 text-kit-2xs text-kit-fg-3",
|
|
83
|
+
children: o.meta
|
|
84
|
+
}) : null
|
|
85
|
+
]
|
|
86
|
+
})] }, o.value))
|
|
87
|
+
}),
|
|
88
|
+
shown.length === 0 && hasEmptyNote ? /* @__PURE__ */ jsx("p", {
|
|
89
|
+
role: "status",
|
|
90
|
+
className: NOTE,
|
|
91
|
+
children: emptyNote
|
|
92
|
+
}) : null,
|
|
93
|
+
shown.length > 0 && listNote != null && listNote !== false ? /* @__PURE__ */ jsx("p", {
|
|
94
|
+
role: "status",
|
|
95
|
+
className: NOTE,
|
|
96
|
+
children: listNote
|
|
97
|
+
}) : null
|
|
98
|
+
] });
|
|
36
99
|
return /* @__PURE__ */ jsxs(Autocomplete$1.Root, {
|
|
37
100
|
items: shown,
|
|
38
101
|
filter: null,
|
|
39
102
|
mode: "list",
|
|
40
103
|
value: text,
|
|
41
|
-
onValueChange: (t) =>
|
|
104
|
+
onValueChange: (t, details) => {
|
|
105
|
+
if (!fillOnSelect && details.reason === "item-press") return;
|
|
106
|
+
setText(t);
|
|
107
|
+
},
|
|
42
108
|
itemToStringValue: (o) => nodeText(o.label),
|
|
43
|
-
open:
|
|
109
|
+
open: visible,
|
|
44
110
|
onOpenChange: setOpen,
|
|
45
|
-
|
|
111
|
+
inline: inlineList,
|
|
112
|
+
autoHighlight: highlightFirst,
|
|
113
|
+
onItemHighlighted: (o) => setHighlighted(o),
|
|
46
114
|
openOnInputClick: true,
|
|
47
115
|
loopFocus: true,
|
|
48
116
|
disabled,
|
|
49
117
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
50
118
|
className: cn("flex min-w-0 items-center rounded-kit-md border border-kit-field-border bg-kit-field-bg shadow-kit-field transition-[border-color,box-shadow] duration-150 hover:border-kit-field-border-hover focus-within:border-kit-field-border-focus focus-within:shadow-kit-ring-field", invalid && "border-[var(--kit-field-invalid)] hover:border-[var(--kit-field-invalid)]", disabled && "cursor-not-allowed bg-kit-subtle opacity-60", H[size], className),
|
|
51
119
|
children: [
|
|
120
|
+
leadingSlot,
|
|
52
121
|
leading ? /* @__PURE__ */ jsx("span", {
|
|
53
122
|
"aria-hidden": true,
|
|
54
123
|
className: "ml-2.5 flex shrink-0 text-kit-fg-3 [&_svg]:size-4",
|
|
55
124
|
children: leading
|
|
56
125
|
}) : null,
|
|
57
126
|
/* @__PURE__ */ jsx(Autocomplete$1.Input, {
|
|
127
|
+
ref: inputRef,
|
|
58
128
|
id: id ?? fieldId,
|
|
59
129
|
"aria-label": ariaLabel,
|
|
60
130
|
"aria-labelledby": ariaLabel ? void 0 : labelFor ?? (named ? void 0 : labelledBy),
|
|
@@ -64,8 +134,20 @@ function Autocomplete({ id, "aria-label": ariaLabel, "aria-describedby": ariaDes
|
|
|
64
134
|
autoComplete: "off",
|
|
65
135
|
spellCheck: false,
|
|
66
136
|
onFocus: () => openOnFocus && setOpen(true),
|
|
137
|
+
onBlur,
|
|
138
|
+
onKeyDownCapture: (e) => {
|
|
139
|
+
if (e.key === "Escape" && visible) {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
setOpen(false);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
67
145
|
onKeyDown: (e) => {
|
|
68
|
-
if (e.key
|
|
146
|
+
if (e.key !== "Enter") return;
|
|
147
|
+
if (firstStandsIn) {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
pick(shown[0]);
|
|
150
|
+
} else if (!highlighted || !visible) onEnter?.(text);
|
|
69
151
|
},
|
|
70
152
|
className: "h-full min-w-0 flex-1 bg-transparent px-2 text-kit-sm text-kit-fg outline-none placeholder:text-kit-fg-3 disabled:cursor-not-allowed"
|
|
71
153
|
}),
|
|
@@ -80,11 +162,17 @@ function Autocomplete({ id, "aria-label": ariaLabel, "aria-describedby": ariaDes
|
|
|
80
162
|
variant: "ghost",
|
|
81
163
|
className: "mr-1",
|
|
82
164
|
onMouseDown: (e) => e.preventDefault(),
|
|
83
|
-
onClick: () => setText(""),
|
|
165
|
+
onClick: () => onClear ? onClear() : setText(""),
|
|
84
166
|
children: /* @__PURE__ */ jsx(IconX, { stroke: 1.5 })
|
|
85
|
-
}) : null
|
|
167
|
+
}) : null,
|
|
168
|
+
trailingSlot
|
|
86
169
|
]
|
|
87
|
-
}),
|
|
170
|
+
}), inlineList ? visible ? /* @__PURE__ */ jsx("div", {
|
|
171
|
+
...listAttrs,
|
|
172
|
+
className: cn("mt-1 rounded-kit-md border border-kit-line bg-kit-surface p-1", menuClassName),
|
|
173
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
174
|
+
children: rows
|
|
175
|
+
}) : null : /* @__PURE__ */ jsx(Autocomplete$1.Portal, { children: /* @__PURE__ */ jsx(Autocomplete$1.Positioner, {
|
|
88
176
|
align: "start",
|
|
89
177
|
side: "bottom",
|
|
90
178
|
sideOffset: 4,
|
|
@@ -92,43 +180,10 @@ function Autocomplete({ id, "aria-label": ariaLabel, "aria-describedby": ariaDes
|
|
|
92
180
|
className: "z-[var(--kit-z-menu,80)]",
|
|
93
181
|
"data-floating-layer": "",
|
|
94
182
|
...KIT_SCOPE,
|
|
183
|
+
...listAttrs,
|
|
95
184
|
children: /* @__PURE__ */ jsx(Autocomplete$1.Popup, {
|
|
96
185
|
className: cn("w-[max(var(--anchor-width),280px)] max-w-[min(420px,calc(100vw-24px))] rounded-kit-lg border bg-kit-surface p-1 text-kit-fg shadow-lg outline-none data-[open]:animate-kit-pop-in", menuClassName),
|
|
97
|
-
children:
|
|
98
|
-
className: "max-h-72 overflow-y-auto overscroll-contain",
|
|
99
|
-
children: shown.map((o, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [o.group && (i === 0 || shown[i - 1].group !== o.group) ? /* @__PURE__ */ jsx("div", {
|
|
100
|
-
role: "presentation",
|
|
101
|
-
className: "px-2 pb-1 pt-2 text-kit-xs font-medium text-kit-fg-3 first:pt-1",
|
|
102
|
-
children: o.group
|
|
103
|
-
}) : null, /* @__PURE__ */ jsxs(Autocomplete$1.Item, {
|
|
104
|
-
value: o,
|
|
105
|
-
disabled: o.disabled,
|
|
106
|
-
onClick: () => onSelect?.(o),
|
|
107
|
-
className: "flex h-8 cursor-pointer select-none items-center gap-2 rounded-kit-md px-2 text-kit-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-40 data-[highlighted]:bg-kit-hover",
|
|
108
|
-
children: [
|
|
109
|
-
o.icon ? /* @__PURE__ */ jsx("span", {
|
|
110
|
-
"aria-hidden": true,
|
|
111
|
-
className: "flex shrink-0 text-kit-fg-3 [&_svg]:size-4",
|
|
112
|
-
children: o.icon
|
|
113
|
-
}) : null,
|
|
114
|
-
/* @__PURE__ */ jsx("span", {
|
|
115
|
-
className: "min-w-0 shrink truncate text-kit-fg",
|
|
116
|
-
children: typeof o.label === "string" ? /* @__PURE__ */ jsx(Highlight, {
|
|
117
|
-
text: o.label,
|
|
118
|
-
query: text
|
|
119
|
-
}) : o.label
|
|
120
|
-
}),
|
|
121
|
-
o.description ? /* @__PURE__ */ jsx("span", {
|
|
122
|
-
className: "min-w-0 flex-1 truncate text-kit-xs text-kit-fg-3",
|
|
123
|
-
children: o.description
|
|
124
|
-
}) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
125
|
-
o.meta ? /* @__PURE__ */ jsx("span", {
|
|
126
|
-
className: "shrink-0 text-kit-2xs text-kit-fg-3",
|
|
127
|
-
children: o.meta
|
|
128
|
-
}) : null
|
|
129
|
-
]
|
|
130
|
-
})] }, o.value))
|
|
131
|
-
})
|
|
186
|
+
children: rows
|
|
132
187
|
})
|
|
133
188
|
}) })]
|
|
134
189
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.js","names":[],"sources":["../../src/ui/autocomplete.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Autocomplete as AutocompletePrimitive } from \"@base-ui/react/autocomplete\";\nimport { IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../lib/cn\";\nimport { IconButton } from \"./button\";\nimport { useFieldDescribedBy, useFieldId, useFieldLabelledBy, useLabelFor } from \"./field-context\";\nimport { filterOptions, Highlight, nodeText, type SelectOption } from \"./select\";\nimport { Spinner } from \"./spinner\";\nimport { KIT_SCOPE } from \"./surfaces\";\n\n/* ── Autocomplete ────────────────────────────────────────────────────────\n Free text with suggestions: the value is whatever is typed; a suggestion fills it in. Select only picks fixed options.\n V2 has no component, only the pattern (kit controls link.tsx:504-650, invite-flow.tsx:197); kit-ports-spec.md §2.\n Field = the Input look; the list opens on focus or typing, only while it has rows (free text is a valid value, so\n there is no \"No matches\"); nothing is highlighted until the arrows move, so Enter commits the typed text (onEnter).\n Ranking is Select's filterOptions + Highlight. */\n\nexport interface AutocompleteProps {\n id?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n /** the text (controlled) */\n value?: string;\n defaultValue?: string;\n /** every keystroke, and the label of a picked suggestion */\n onValueChange?: (text: string) => void;\n options: SelectOption[];\n /** a suggestion was picked (click, or Enter on a highlighted row) */\n onSelect?: (option: SelectOption) => void;\n /** Enter with no suggestion highlighted: commit the typed text */\n onEnter?: (text: string) => void;\n /** default: Select's ranking; null = the caller already filtered (async options) */\n filter?: ((options: SelectOption[], query: string) => SelectOption[]) | null;\n placeholder?: string;\n size?: \"sm\" | \"md\" | \"lg\";\n leading?: React.ReactNode;\n loading?: boolean;\n invalid?: boolean;\n disabled?: boolean;\n /** open the suggestions when the field takes focus (default true, as V2) */\n openOnFocus?: boolean;\n className?: string;\n menuClassName?: string;\n}\n\nconst H = { sm: \"h-7\", md: \"h-8\", lg: \"h-9\" } as const;\n\nexport function Autocomplete({\n id,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": ariaDescribedBy,\n value,\n defaultValue = \"\",\n onValueChange,\n options,\n onSelect,\n onEnter,\n filter = filterOptions,\n placeholder,\n size = \"md\",\n leading,\n loading,\n invalid,\n disabled,\n openOnFocus = true,\n className,\n menuClassName,\n}: AutocompleteProps) {\n const fieldId = useFieldId();\n const labelledBy = useFieldLabelledBy();\n const labelFor = useLabelFor(id ?? fieldId);\n const describedBy = useFieldDescribedBy();\n const [inner, setInner] = React.useState(defaultValue);\n const text = value ?? inner;\n const setText = (t: string) => {\n if (value === undefined) setInner(t);\n onValueChange?.(t);\n };\n const [open, setOpen] = React.useState(false);\n const highlighted = React.useRef<SelectOption | undefined>(undefined);\n const shown = React.useMemo(() => (filter ? filter(options, text) : options), [filter, options, text]);\n const named = id ?? fieldId ?? ariaLabel;\n\n return (\n <AutocompletePrimitive.Root\n items={shown}\n filter={null}\n mode=\"list\"\n value={text}\n onValueChange={(t) => setText(t)}\n itemToStringValue={(o: SelectOption) => nodeText(o.label)}\n open={open && shown.length > 0}\n onOpenChange={setOpen}\n onItemHighlighted={(o) => (highlighted.current = o as SelectOption | undefined)}\n openOnInputClick\n loopFocus\n disabled={disabled}\n >\n <div\n className={cn(\n \"flex min-w-0 items-center rounded-kit-md border border-kit-field-border bg-kit-field-bg shadow-kit-field transition-[border-color,box-shadow] duration-150 hover:border-kit-field-border-hover focus-within:border-kit-field-border-focus focus-within:shadow-kit-ring-field\",\n invalid && \"border-[var(--kit-field-invalid)] hover:border-[var(--kit-field-invalid)]\",\n disabled && \"cursor-not-allowed bg-kit-subtle opacity-60\",\n H[size],\n className,\n )}\n >\n {leading ? (\n <span aria-hidden className=\"ml-2.5 flex shrink-0 text-kit-fg-3 [&_svg]:size-4\">\n {leading}\n </span>\n ) : null}\n <AutocompletePrimitive.Input\n id={id ?? fieldId}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabel ? undefined : (labelFor ?? (named ? undefined : labelledBy))}\n aria-describedby={[ariaDescribedBy, describedBy].filter(Boolean).join(\" \") || undefined}\n aria-invalid={invalid || undefined}\n placeholder={placeholder}\n autoComplete=\"off\"\n spellCheck={false}\n onFocus={() => openOnFocus && setOpen(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !highlighted.current) onEnter?.(text);\n }}\n className=\"h-full min-w-0 flex-1 bg-transparent px-2 text-kit-sm text-kit-fg outline-none placeholder:text-kit-fg-3 disabled:cursor-not-allowed\"\n />\n {loading ? (\n <Spinner size=\"sm\" tone=\"muted\" label=\"Loading suggestions\" className=\"mr-2\" />\n ) : text && !disabled ? (\n <IconButton label=\"Clear\" size=\"xs\" variant=\"ghost\" className=\"mr-1\" onMouseDown={(e) => e.preventDefault()} onClick={() => setText(\"\")}>\n <IconX stroke={1.5} />\n </IconButton>\n ) : null}\n </div>\n <AutocompletePrimitive.Portal>\n <AutocompletePrimitive.Positioner align=\"start\" side=\"bottom\" sideOffset={4} collisionPadding={12} className=\"z-[var(--kit-z-menu,80)]\" data-floating-layer=\"\" {...KIT_SCOPE}>\n <AutocompletePrimitive.Popup\n className={cn(\n \"w-[max(var(--anchor-width),280px)] max-w-[min(420px,calc(100vw-24px))] rounded-kit-lg border bg-kit-surface p-1 text-kit-fg shadow-lg outline-none data-[open]:animate-kit-pop-in\",\n menuClassName,\n )}\n >\n <AutocompletePrimitive.List className=\"max-h-72 overflow-y-auto overscroll-contain\">\n {shown.map((o, i) => (\n <React.Fragment key={o.value}>\n {o.group && (i === 0 || shown[i - 1].group !== o.group) ? (\n <div role=\"presentation\" className=\"px-2 pb-1 pt-2 text-kit-xs font-medium text-kit-fg-3 first:pt-1\">\n {o.group}\n </div>\n ) : null}\n <AutocompletePrimitive.Item\n value={o}\n disabled={o.disabled}\n onClick={() => onSelect?.(o)}\n className=\"flex h-8 cursor-pointer select-none items-center gap-2 rounded-kit-md px-2 text-kit-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-40 data-[highlighted]:bg-kit-hover\"\n >\n {o.icon ? (\n <span aria-hidden className=\"flex shrink-0 text-kit-fg-3 [&_svg]:size-4\">\n {o.icon}\n </span>\n ) : null}\n <span className=\"min-w-0 shrink truncate text-kit-fg\">\n {typeof o.label === \"string\" ? <Highlight text={o.label} query={text} /> : o.label}\n </span>\n {o.description ? <span className=\"min-w-0 flex-1 truncate text-kit-xs text-kit-fg-3\">{o.description}</span> : <span className=\"flex-1\" />}\n {o.meta ? <span className=\"shrink-0 text-kit-2xs text-kit-fg-3\">{o.meta}</span> : null}\n </AutocompletePrimitive.Item>\n </React.Fragment>\n ))}\n </AutocompletePrimitive.List>\n </AutocompletePrimitive.Popup>\n </AutocompletePrimitive.Positioner>\n </AutocompletePrimitive.Portal>\n </AutocompletePrimitive.Root>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"autocomplete.js","names":[],"sources":["../../src/ui/autocomplete.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Autocomplete as AutocompletePrimitive } from \"@base-ui/react/autocomplete\";\nimport { IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../lib/cn\";\nimport { IconButton } from \"./button\";\nimport { useFieldDescribedBy, useFieldId, useFieldLabelledBy, useLabelFor } from \"./field-context\";\nimport { filterOptions, Highlight, nodeText, type SelectOption } from \"./select\";\nimport { Spinner } from \"./spinner\";\nimport { KIT_SCOPE } from \"./surfaces\";\n\n/* ── Autocomplete ────────────────────────────────────────────────────────\n Free text with suggestions: the value is whatever is typed; a suggestion fills it in. Select only picks fixed options.\n V2 has no component, only the pattern (kit controls link.tsx:504-650, invite-flow.tsx:197); kit-ports-spec.md §2.\n Field = the Input look; the list opens on focus or typing, only while it has rows (free text is a valid value, so\n there is no \"No matches\"); nothing is highlighted until the arrows move, so Enter commits the typed text (onEnter).\n Ranking is Select's filterOptions + Highlight.\n The link field (LinkPicker) opts into V2 link.tsx's extras: an in-flow list (inlineList), a status note when nothing\n matches (emptyNote) or under the rows (listNote), the link-type menu inside the field (leadingSlot), row 0 highlighted\n while typing (highlightFirst), a controlled open, plain row titles (highlightMatches={false}), and Escape that closes\n only the list while it is open (link.tsx:603-618). */\n\nexport interface AutocompleteProps {\n id?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n /** the text (controlled) */\n value?: string;\n defaultValue?: string;\n /** every keystroke, and the label of a picked suggestion */\n onValueChange?: (text: string) => void;\n options: SelectOption[];\n /** a suggestion was picked (click, or Enter on a highlighted row) */\n onSelect?: (option: SelectOption) => void;\n /** Enter with no suggestion highlighted: commit the typed text */\n onEnter?: (text: string) => void;\n /** default: Select's ranking; null = the caller already filtered (async options) */\n filter?: ((options: SelectOption[], query: string) => SelectOption[]) | null;\n placeholder?: string;\n size?: \"sm\" | \"md\" | \"lg\";\n /** decorative icon before the input */\n leading?: React.ReactNode;\n /** an interactive control inside the field before the input (the link-type menu); rendered as is */\n leadingSlot?: React.ReactNode;\n loading?: boolean;\n invalid?: boolean;\n disabled?: boolean;\n /** open the suggestions when the field takes focus (default true, as V2) */\n openOnFocus?: boolean;\n /** controlled open; the list shows while open and it has rows or a note */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** render the list in the flow under the field instead of a floating popup (panels, popovers) */\n inlineList?: boolean;\n /** a status row shown, with the list kept open, when no row matches */\n emptyNote?: React.ReactNode;\n /** a status row under the rows when there are rows (a partial-load error) */\n listNote?: React.ReactNode;\n /** highlight row 0 on every keystroke that leaves text, so Enter picks it; empty text highlights nothing */\n highlightFirst?: boolean;\n /** emphasise the query inside row labels (default true; V2's link rows draw the plain title) */\n highlightMatches?: boolean;\n /** the listbox's accessible name */\n listLabel?: string;\n /** a pick also writes the row's label into the text (default true); false = a pick only calls onSelect */\n fillOnSelect?: boolean;\n /** replaces the built-in clear (setText(\"\")) on the X */\n onClear?: () => void;\n /** a control inside the field after the clear button (the file-library button) */\n trailingSlot?: React.ReactNode;\n /** data-* attributes on the list's layer (the floating positioner, or the inline wrapper) */\n listAttrs?: Record<`data-${string}`, string>;\n inputRef?: React.Ref<HTMLInputElement>;\n onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;\n className?: string;\n menuClassName?: string;\n}\n\nconst H = { sm: \"h-7\", md: \"h-8\", lg: \"h-9\" } as const;\n/* V2 link.tsx:507-509 */\nconst NOTE = \"px-2 py-1.5 text-kit-xs text-kit-fg-3\";\n\nexport function Autocomplete({\n id,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": ariaDescribedBy,\n value,\n defaultValue = \"\",\n onValueChange,\n options,\n onSelect,\n onEnter,\n filter = filterOptions,\n placeholder,\n size = \"md\",\n leading,\n leadingSlot,\n loading,\n invalid,\n disabled,\n openOnFocus = true,\n open: openProp,\n onOpenChange,\n inlineList = false,\n emptyNote,\n listNote,\n highlightFirst = false,\n highlightMatches = true,\n listLabel,\n fillOnSelect = true,\n onClear,\n trailingSlot,\n listAttrs,\n inputRef,\n onBlur,\n className,\n menuClassName,\n}: AutocompleteProps) {\n const fieldId = useFieldId();\n const labelledBy = useFieldLabelledBy();\n const labelFor = useLabelFor(id ?? fieldId);\n const describedBy = useFieldDescribedBy();\n const [inner, setInner] = React.useState(defaultValue);\n const text = value ?? inner;\n const setText = (t: string) => {\n if (value === undefined) setInner(t);\n onValueChange?.(t);\n };\n const [innerOpen, setInnerOpen] = React.useState(false);\n const open = openProp ?? innerOpen;\n const setOpen = (o: boolean) => {\n if (openProp === undefined) setInnerOpen(o);\n onOpenChange?.(o);\n };\n const [highlighted, setHighlighted] = React.useState<SelectOption | undefined>(undefined);\n const shown = React.useMemo(() => (filter ? filter(options, text) : options), [filter, options, text]);\n const hasEmptyNote = emptyNote != null && emptyNote !== false;\n const visible = open && (shown.length > 0 || hasEmptyNote);\n /* highlightFirst: row 0 stands in as the active row while text is typed and nothing else is highlighted (V2\n link.tsx sets its own active index to 0 on each keystroke); Base UI's autoHighlight normally does the same. */\n const firstStandsIn = highlightFirst && visible && text.trim() !== \"\" && !highlighted && shown.length > 0 && !shown[0].disabled;\n const pick = (o: SelectOption) => {\n onSelect?.(o);\n if (fillOnSelect) setText(nodeText(o.label));\n setOpen(false);\n };\n const named = id ?? fieldId ?? ariaLabel;\n\n const rows = (\n <>\n <AutocompletePrimitive.List aria-label={listLabel} className=\"max-h-72 overflow-y-auto overscroll-contain empty:hidden\">\n {shown.map((o, i) => (\n <React.Fragment key={o.value}>\n {o.group && (i === 0 || shown[i - 1].group !== o.group) ? (\n <div role=\"presentation\" className=\"px-2 pb-1 pt-2 text-kit-xs font-medium text-kit-fg-3 first:pt-1\">\n {o.group}\n </div>\n ) : null}\n <AutocompletePrimitive.Item\n value={o}\n disabled={o.disabled}\n onClick={() => onSelect?.(o)}\n data-first-active={firstStandsIn && i === 0 ? \"\" : undefined}\n className=\"flex h-8 cursor-pointer select-none items-center gap-2 rounded-kit-md px-2 text-kit-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-40 data-[highlighted]:bg-kit-hover data-[first-active]:bg-kit-hover\"\n >\n {o.icon ? (\n <span aria-hidden className=\"flex shrink-0 text-kit-fg-3 [&_svg]:size-4\">\n {o.icon}\n </span>\n ) : null}\n <span className=\"min-w-0 shrink truncate text-kit-fg\">\n {highlightMatches && typeof o.label === \"string\" ? <Highlight text={o.label} query={text} /> : o.label}\n </span>\n {o.description ? <span className=\"min-w-0 flex-1 truncate text-kit-xs text-kit-fg-3\">{o.description}</span> : <span className=\"flex-1\" />}\n {o.meta ? <span className=\"shrink-0 text-kit-2xs text-kit-fg-3\">{o.meta}</span> : null}\n </AutocompletePrimitive.Item>\n </React.Fragment>\n ))}\n </AutocompletePrimitive.List>\n {shown.length === 0 && hasEmptyNote ? (\n <p role=\"status\" className={NOTE}>\n {emptyNote}\n </p>\n ) : null}\n {shown.length > 0 && listNote != null && listNote !== false ? (\n <p role=\"status\" className={NOTE}>\n {listNote}\n </p>\n ) : null}\n </>\n );\n\n return (\n <AutocompletePrimitive.Root\n items={shown}\n filter={null}\n mode=\"list\"\n value={text}\n onValueChange={(t, details) => {\n // Base UI writes a pressed row's label into the input; a host that swaps the field for a chip opts out\n if (!fillOnSelect && details.reason === \"item-press\") return;\n setText(t);\n }}\n itemToStringValue={(o: SelectOption) => nodeText(o.label)}\n open={visible}\n onOpenChange={setOpen}\n inline={inlineList}\n // highlight row 0 while typing; Base UI highlights nothing for empty text, so Enter there commits it (onEnter)\n autoHighlight={highlightFirst}\n onItemHighlighted={(o) => setHighlighted(o as SelectOption | undefined)}\n openOnInputClick\n loopFocus\n disabled={disabled}\n >\n <div\n className={cn(\n \"flex min-w-0 items-center rounded-kit-md border border-kit-field-border bg-kit-field-bg shadow-kit-field transition-[border-color,box-shadow] duration-150 hover:border-kit-field-border-hover focus-within:border-kit-field-border-focus focus-within:shadow-kit-ring-field\",\n invalid && \"border-[var(--kit-field-invalid)] hover:border-[var(--kit-field-invalid)]\",\n disabled && \"cursor-not-allowed bg-kit-subtle opacity-60\",\n H[size],\n className,\n )}\n >\n {leadingSlot}\n {leading ? (\n <span aria-hidden className=\"ml-2.5 flex shrink-0 text-kit-fg-3 [&_svg]:size-4\">\n {leading}\n </span>\n ) : null}\n <AutocompletePrimitive.Input\n ref={inputRef}\n id={id ?? fieldId}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabel ? undefined : (labelFor ?? (named ? undefined : labelledBy))}\n aria-describedby={[ariaDescribedBy, describedBy].filter(Boolean).join(\" \") || undefined}\n aria-invalid={invalid || undefined}\n placeholder={placeholder}\n autoComplete=\"off\"\n spellCheck={false}\n onFocus={() => openOnFocus && setOpen(true)}\n onBlur={onBlur}\n onKeyDownCapture={(e) => {\n // V2 link.tsx:603-618: while the list shows, Escape closes only the list; the next one reaches the panel\n if (e.key === \"Escape\" && visible) {\n e.preventDefault();\n e.stopPropagation();\n setOpen(false);\n }\n }}\n onKeyDown={(e) => {\n if (e.key !== \"Enter\") return;\n if (firstStandsIn) {\n e.preventDefault();\n pick(shown[0]);\n } else if (!highlighted || !visible) onEnter?.(text);\n }}\n className=\"h-full min-w-0 flex-1 bg-transparent px-2 text-kit-sm text-kit-fg outline-none placeholder:text-kit-fg-3 disabled:cursor-not-allowed\"\n />\n {loading ? (\n <Spinner size=\"sm\" tone=\"muted\" label=\"Loading suggestions\" className=\"mr-2\" />\n ) : text && !disabled ? (\n <IconButton label=\"Clear\" size=\"xs\" variant=\"ghost\" className=\"mr-1\" onMouseDown={(e) => e.preventDefault()} onClick={() => (onClear ? onClear() : setText(\"\"))}>\n <IconX stroke={1.5} />\n </IconButton>\n ) : null}\n {trailingSlot}\n </div>\n {inlineList ? (\n visible ? (\n /* V2 link.tsx:646-649: the in-flow list under the field */\n <div {...listAttrs} className={cn(\"mt-1 rounded-kit-md border border-kit-line bg-kit-surface p-1\", menuClassName)} onMouseDown={(e) => e.preventDefault()}>\n {rows}\n </div>\n ) : null\n ) : (\n <AutocompletePrimitive.Portal>\n <AutocompletePrimitive.Positioner align=\"start\" side=\"bottom\" sideOffset={4} collisionPadding={12} className=\"z-[var(--kit-z-menu,80)]\" data-floating-layer=\"\" {...KIT_SCOPE} {...listAttrs}>\n <AutocompletePrimitive.Popup\n className={cn(\n \"w-[max(var(--anchor-width),280px)] max-w-[min(420px,calc(100vw-24px))] rounded-kit-lg border bg-kit-surface p-1 text-kit-fg shadow-lg outline-none data-[open]:animate-kit-pop-in\",\n menuClassName,\n )}\n >\n {rows}\n </AutocompletePrimitive.Popup>\n </AutocompletePrimitive.Positioner>\n </AutocompletePrimitive.Portal>\n )}\n </AutocompletePrimitive.Root>\n );\n}\n"],"mappings":"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"}
|
package/dist/ui/button.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { SPARKLE_PATH } from "../brand/logo.js";
|
|
3
|
+
import { Spinner } from "./spinner.js";
|
|
3
4
|
import { TooltipLayer, TooltipRoot } from "./tooltip-root.js";
|
|
4
5
|
import { variants } from "./variants.js";
|
|
5
6
|
import * as React from "react";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import {
|
|
8
|
+
import { IconPlus } from "@tabler/icons-react";
|
|
8
9
|
import { Tooltip } from "@base-ui/react/tooltip";
|
|
9
10
|
//#region src/ui/button.tsx
|
|
10
11
|
/**
|
|
@@ -69,10 +70,7 @@ var Button = React.forwardRef(({ className, variant, size, asChild, loading, lef
|
|
|
69
70
|
className: classes,
|
|
70
71
|
...props,
|
|
71
72
|
children: [
|
|
72
|
-
loading ? /* @__PURE__ */ jsx(
|
|
73
|
-
className: "animate-spin",
|
|
74
|
-
"aria-hidden": true
|
|
75
|
-
}) : leftIcon,
|
|
73
|
+
loading ? /* @__PURE__ */ jsx(Spinner, {}) : leftIcon,
|
|
76
74
|
variant === "ai" && !leftIcon && !loading ? /* @__PURE__ */ jsx(Sparkle, {}) : null,
|
|
77
75
|
children,
|
|
78
76
|
rightIcon
|
package/dist/ui/button.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","names":[],"sources":["../../src/ui/button.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\";\nimport { IconLoader2, IconPlus } from \"@tabler/icons-react\";\nimport { cn } from \"../lib/cn\";\nimport { SPARKLE_PATH } from \"../brand/logo\";\nimport { TooltipLayer, TooltipRoot } from \"./tooltip-root\";\nimport { variants, type VariantProps } from \"./variants\";\n\n/**\n * Button: the only button in the kit.\n * Heights: xs 24 · sm 28 · md 32 (default) · lg 36 · xl 40. Radius 6.\n * Variants: primary (ink) · accent (violet, reserved for the ONE create action per screen)\n * secondary (bordered) · ghost · soft · danger · danger-ghost · link · ai (gradient sparkle)\n * The keyboard focus ring is the global :focus-visible outline, suppressed under pointer modality (DESIGN.md 3.7).\n */\nexport const buttonVariants = variants(\n \"relative inline-flex items-center justify-center gap-1.5 whitespace-nowrap font-medium tracking-[-0.01em] select-none transition-[background-color,border-color,color,box-shadow,opacity] duration-150 ease-out disabled:pointer-events-none disabled:opacity-45 [&_svg]:shrink-0 [&_svg]:opacity-90\",\n {\n variants: {\n variant: {\n // .btn-solid: in dark a disabled primary/accent takes the solid --kit-disabled-bg / --kit-disabled-fg pair; light keeps the opacity dim.\n primary: \"bg-kit-inverse text-kit-fg-inverse shadow-kit-button-primary hover:bg-kit-inverse-hover active:bg-kit-inverse btn-solid\",\n accent: \"bg-kit-accent text-kit-accent-on shadow-kit-xs hover:bg-kit-accent-hover btn-solid\",\n secondary: \"border border-kit-line-strong bg-kit-control-bg text-kit-fg shadow-kit-xs hover:bg-kit-subtle active:bg-kit-hover\",\n ghost: \"text-kit-fg-2 hover:bg-kit-hover hover:text-kit-fg active:bg-kit-selected\",\n soft: \"bg-kit-muted text-kit-fg hover:bg-kit-selected\",\n danger: \"bg-kit-danger text-kit-danger-on shadow-kit-xs hover:brightness-95\",\n \"danger-ghost\": \"text-kit-danger-fg hover:bg-kit-danger-soft\",\n link: \"text-kit-accent-fg underline-offset-4 hover:underline px-0 h-auto active:scale-100\",\n ai: \"border border-kit-line bg-kit-control-bg text-kit-fg shadow-kit-xs hover:border-kit-accent-line hover:bg-kit-accent-softer\",\n },\n size: {\n xs: \"h-6 rounded-kit-md px-2 text-kit-xs [&_svg]:size-3.5\",\n sm: \"h-7 rounded-kit-md px-2.5 text-kit-sm [&_svg]:size-3.5\",\n md: \"h-8 rounded-kit-md px-3 text-kit-sm [&_svg]:size-4\",\n lg: \"h-9 rounded-kit-md px-3.5 text-kit-base [&_svg]:size-4\",\n xl: \"h-10 rounded-kit-md px-4 text-kit-base [&_svg]:size-[18px]\",\n },\n },\n compoundVariants: [{ variant: \"link\", className: \"h-auto px-0\" }],\n defaultVariants: { variant: \"secondary\", size: \"md\" },\n },\n);\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Render the single child element (a link, say) with the button's classes and props. */\n asChild?: boolean;\n loading?: boolean;\n leftIcon?: React.ReactNode;\n rightIcon?: React.ReactNode;\n}\n\ntype ChildProps = { className?: string; ref?: React.Ref<unknown> };\n\nfunction mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]) {\n return (node: T | null) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.RefObject<T | null>).current = node;\n }\n };\n}\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild, loading, leftIcon, rightIcon, children, disabled, ...props }, ref) => {\n const classes = cn(buttonVariants({ variant, size }), className);\n if (asChild && React.isValidElement<ChildProps>(children)) {\n return React.cloneElement(children, {\n ...props,\n ...children.props,\n className: cn(classes, children.props.className),\n ref: mergeRefs(ref as React.Ref<unknown>, children.props.ref),\n });\n }\n return (\n <button ref={ref} type={props.type ?? \"button\"} disabled={disabled || loading} aria-busy={loading || undefined} className={classes} {...props}>\n {loading ? <IconLoader2 className=\"animate-spin\" aria-hidden /> : leftIcon}\n {variant === \"ai\" && !leftIcon && !loading ? <Sparkle /> : null}\n {children}\n {rightIcon}\n </button>\n );\n },\n);\nButton.displayName = \"Button\";\n\n/** Square icon-only button. Always pass `label` (used for aria-label + tooltip). */\nexport const iconButtonVariants = variants(\n \"relative inline-flex items-center justify-center shrink-0 text-kit-fg-2 transition-colors duration-150 hover:bg-kit-hover hover:text-kit-fg active:scale-[0.96] disabled:opacity-40 disabled:pointer-events-none data-[active=true]:bg-kit-selected data-[active=true]:text-kit-fg\",\n {\n variants: {\n size: {\n xs: \"size-6 rounded-kit-md [&_svg]:size-3.5\",\n sm: \"size-7 rounded-kit-md [&_svg]:size-4\",\n md: \"size-8 rounded-kit-md [&_svg]:size-4\",\n lg: \"size-9 rounded-kit-md [&_svg]:size-[18px]\",\n },\n variant: {\n ghost: \"\",\n outline: \"border border-kit-line bg-kit-control-bg shadow-kit-xs hover:border-kit-line-strong\",\n soft: \"bg-kit-muted\",\n },\n },\n defaultVariants: { size: \"md\", variant: \"ghost\" },\n },\n);\n\n/**\n * a11y: true while rendering inside a tooltip (or a surface that manages its own hints), so an IconButton nested\n * there doesn't add a second tooltip. Provided by HintTooltip and overlay.tsx's <Tooltip>.\n */\nexport const TooltipScope = React.createContext(false);\n\n/**\n * The plain ink hint (DESIGN.md 8.23: top, 6px offset, 8px collision padding, portalled, z 90, 350ms). The child\n * element is the trigger and must take a ref and DOM props. input, display and action-tray use it so they never\n * import overlay.tsx; overlay's <Tooltip> adds the shortcut slot on top of the same layer.\n */\nexport function HintTooltip({\n content,\n side = \"top\",\n delay = 350,\n children,\n}: {\n content: React.ReactNode;\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n delay?: number;\n children: React.ReactElement;\n}) {\n return (\n <TooltipScope.Provider value>\n <TooltipRoot>\n <TooltipPrimitive.Trigger delay={delay} render={children} />\n <TooltipLayer side={side}>\n <span className=\"line-clamp-[8] min-w-0 whitespace-normal [overflow-wrap:anywhere]\">{content}</span>\n </TooltipLayer>\n </TooltipRoot>\n </TooltipScope.Provider>\n );\n}\n\nexport interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof iconButtonVariants> {\n label: string;\n active?: boolean;\n /** Hover/focus hint. Defaults to `label`; pass `false` to opt out, or a node to customise. */\n tooltip?: React.ReactNode | false;\n tooltipSide?: \"top\" | \"bottom\" | \"left\" | \"right\";\n}\n\nexport const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(\n ({ className, size, variant, label, active, children, tooltip, tooltipSide = \"top\", ...props }, ref) => {\n const inTooltip = React.useContext(TooltipScope);\n const btn = (\n <button\n ref={ref}\n type=\"button\"\n aria-label={label}\n data-active={active ? \"true\" : undefined}\n className={cn(iconButtonVariants({ size, variant }), className)}\n {...props}\n >\n {children}\n </button>\n );\n // Every icon-only button gets a visible hint on hover AND keyboard focus (brief §11), unless a\n // parent tooltip already provides one or the caller opted out.\n if (inTooltip || tooltip === false || !label) return btn;\n return (\n <HintTooltip content={tooltip ?? label} side={tooltipSide}>\n {btn}\n </HintTooltip>\n );\n },\n);\nIconButton.displayName = \"IconButton\";\n\n/** The AI sparkle in the official brand gradient. Used everywhere AI appears. */\nexport function Sparkle({ className, size = 16 }: { className?: string; size?: number }) {\n const id = React.useId().replace(/:/g, \"\");\n return (\n <svg width={size} height={size} viewBox=\"2 2 16 16\" className={cn(\"shrink-0\", className)} aria-hidden>\n <defs>\n <linearGradient id={`sp${id}`} x1=\"6.89\" y1=\"4.59\" x2=\"18\" y2=\"23.67\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0.0817\" style={{ stopColor: \"var(--kit-brand-violet)\" }} />\n <stop offset=\"0.4667\" style={{ stopColor: \"var(--kit-brand-coral)\" }} />\n <stop offset=\"1\" style={{ stopColor: \"var(--kit-brand-rose)\" }} />\n </linearGradient>\n </defs>\n <path d={SPARKLE_PATH} fill={`url(#sp${id})`} />\n </svg>\n );\n}\n\n/** D15 progressive disclosure: \"+ Add a message\" / \"+ Limit to specific sites\": reveals an optional input. */\nexport function RevealLink({ className, children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n return (\n <button\n type=\"button\"\n className={cn(\n \"inline-flex h-7 items-center gap-1 rounded-kit-md text-kit-sm font-medium text-kit-fg-2 transition-colors hover:text-kit-fg [&_svg]:size-3.5\",\n className,\n )}\n {...props}\n >\n <IconPlus stroke={2} aria-hidden />\n {children}\n </button>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.js","names":[],"sources":["../../src/ui/button.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\";\nimport { IconPlus } from \"@tabler/icons-react\";\nimport { cn } from \"../lib/cn\";\nimport { SPARKLE_PATH } from \"../brand/logo\";\nimport { Spinner } from \"./spinner\";\nimport { TooltipLayer, TooltipRoot } from \"./tooltip-root\";\nimport { variants, type VariantProps } from \"./variants\";\n\n/**\n * Button: the only button in the kit.\n * Heights: xs 24 · sm 28 · md 32 (default) · lg 36 · xl 40. Radius 6.\n * Variants: primary (ink) · accent (violet, reserved for the ONE create action per screen)\n * secondary (bordered) · ghost · soft · danger · danger-ghost · link · ai (gradient sparkle)\n * The keyboard focus ring is the global :focus-visible outline, suppressed under pointer modality (DESIGN.md 3.7).\n */\nexport const buttonVariants = variants(\n \"relative inline-flex items-center justify-center gap-1.5 whitespace-nowrap font-medium tracking-[-0.01em] select-none transition-[background-color,border-color,color,box-shadow,opacity] duration-150 ease-out disabled:pointer-events-none disabled:opacity-45 [&_svg]:shrink-0 [&_svg]:opacity-90\",\n {\n variants: {\n variant: {\n // .btn-solid: in dark a disabled primary/accent takes the solid --kit-disabled-bg / --kit-disabled-fg pair; light keeps the opacity dim.\n primary: \"bg-kit-inverse text-kit-fg-inverse shadow-kit-button-primary hover:bg-kit-inverse-hover active:bg-kit-inverse btn-solid\",\n accent: \"bg-kit-accent text-kit-accent-on shadow-kit-xs hover:bg-kit-accent-hover btn-solid\",\n secondary: \"border border-kit-line-strong bg-kit-control-bg text-kit-fg shadow-kit-xs hover:bg-kit-subtle active:bg-kit-hover\",\n ghost: \"text-kit-fg-2 hover:bg-kit-hover hover:text-kit-fg active:bg-kit-selected\",\n soft: \"bg-kit-muted text-kit-fg hover:bg-kit-selected\",\n danger: \"bg-kit-danger text-kit-danger-on shadow-kit-xs hover:brightness-95\",\n \"danger-ghost\": \"text-kit-danger-fg hover:bg-kit-danger-soft\",\n link: \"text-kit-accent-fg underline-offset-4 hover:underline px-0 h-auto active:scale-100\",\n ai: \"border border-kit-line bg-kit-control-bg text-kit-fg shadow-kit-xs hover:border-kit-accent-line hover:bg-kit-accent-softer\",\n },\n size: {\n xs: \"h-6 rounded-kit-md px-2 text-kit-xs [&_svg]:size-3.5\",\n sm: \"h-7 rounded-kit-md px-2.5 text-kit-sm [&_svg]:size-3.5\",\n md: \"h-8 rounded-kit-md px-3 text-kit-sm [&_svg]:size-4\",\n lg: \"h-9 rounded-kit-md px-3.5 text-kit-base [&_svg]:size-4\",\n xl: \"h-10 rounded-kit-md px-4 text-kit-base [&_svg]:size-[18px]\",\n },\n },\n compoundVariants: [{ variant: \"link\", className: \"h-auto px-0\" }],\n defaultVariants: { variant: \"secondary\", size: \"md\" },\n },\n);\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Render the single child element (a link, say) with the button's classes and props. */\n asChild?: boolean;\n loading?: boolean;\n leftIcon?: React.ReactNode;\n rightIcon?: React.ReactNode;\n}\n\ntype ChildProps = { className?: string; ref?: React.Ref<unknown> };\n\nfunction mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]) {\n return (node: T | null) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.RefObject<T | null>).current = node;\n }\n };\n}\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild, loading, leftIcon, rightIcon, children, disabled, ...props }, ref) => {\n const classes = cn(buttonVariants({ variant, size }), className);\n if (asChild && React.isValidElement<ChildProps>(children)) {\n return React.cloneElement(children, {\n ...props,\n ...children.props,\n className: cn(classes, children.props.className),\n ref: mergeRefs(ref as React.Ref<unknown>, children.props.ref),\n });\n }\n return (\n <button ref={ref} type={props.type ?? \"button\"} disabled={disabled || loading} aria-busy={loading || undefined} className={classes} {...props}>\n {/* the kit Spinner (decorative: the button says it is busy); the size variant's [&_svg] rule sizes its glyph */}\n {loading ? <Spinner /> : leftIcon}\n {variant === \"ai\" && !leftIcon && !loading ? <Sparkle /> : null}\n {children}\n {rightIcon}\n </button>\n );\n },\n);\nButton.displayName = \"Button\";\n\n/** Square icon-only button. Always pass `label` (used for aria-label + tooltip). */\nexport const iconButtonVariants = variants(\n \"relative inline-flex items-center justify-center shrink-0 text-kit-fg-2 transition-colors duration-150 hover:bg-kit-hover hover:text-kit-fg active:scale-[0.96] disabled:opacity-40 disabled:pointer-events-none data-[active=true]:bg-kit-selected data-[active=true]:text-kit-fg\",\n {\n variants: {\n size: {\n xs: \"size-6 rounded-kit-md [&_svg]:size-3.5\",\n sm: \"size-7 rounded-kit-md [&_svg]:size-4\",\n md: \"size-8 rounded-kit-md [&_svg]:size-4\",\n lg: \"size-9 rounded-kit-md [&_svg]:size-[18px]\",\n },\n variant: {\n ghost: \"\",\n outline: \"border border-kit-line bg-kit-control-bg shadow-kit-xs hover:border-kit-line-strong\",\n soft: \"bg-kit-muted\",\n },\n },\n defaultVariants: { size: \"md\", variant: \"ghost\" },\n },\n);\n\n/**\n * a11y: true while rendering inside a tooltip (or a surface that manages its own hints), so an IconButton nested\n * there doesn't add a second tooltip. Provided by HintTooltip and overlay.tsx's <Tooltip>.\n */\nexport const TooltipScope = React.createContext(false);\n\n/**\n * The plain ink hint (DESIGN.md 8.23: top, 6px offset, 8px collision padding, portalled, z 90, 350ms). The child\n * element is the trigger and must take a ref and DOM props. input, display and action-tray use it so they never\n * import overlay.tsx; overlay's <Tooltip> adds the shortcut slot on top of the same layer.\n */\nexport function HintTooltip({\n content,\n side = \"top\",\n delay = 350,\n children,\n}: {\n content: React.ReactNode;\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n delay?: number;\n children: React.ReactElement;\n}) {\n return (\n <TooltipScope.Provider value>\n <TooltipRoot>\n <TooltipPrimitive.Trigger delay={delay} render={children} />\n <TooltipLayer side={side}>\n <span className=\"line-clamp-[8] min-w-0 whitespace-normal [overflow-wrap:anywhere]\">{content}</span>\n </TooltipLayer>\n </TooltipRoot>\n </TooltipScope.Provider>\n );\n}\n\nexport interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof iconButtonVariants> {\n label: string;\n active?: boolean;\n /** Hover/focus hint. Defaults to `label`; pass `false` to opt out, or a node to customise. */\n tooltip?: React.ReactNode | false;\n tooltipSide?: \"top\" | \"bottom\" | \"left\" | \"right\";\n}\n\nexport const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(\n ({ className, size, variant, label, active, children, tooltip, tooltipSide = \"top\", ...props }, ref) => {\n const inTooltip = React.useContext(TooltipScope);\n const btn = (\n <button\n ref={ref}\n type=\"button\"\n aria-label={label}\n data-active={active ? \"true\" : undefined}\n className={cn(iconButtonVariants({ size, variant }), className)}\n {...props}\n >\n {children}\n </button>\n );\n // Every icon-only button gets a visible hint on hover AND keyboard focus (brief §11), unless a\n // parent tooltip already provides one or the caller opted out.\n if (inTooltip || tooltip === false || !label) return btn;\n return (\n <HintTooltip content={tooltip ?? label} side={tooltipSide}>\n {btn}\n </HintTooltip>\n );\n },\n);\nIconButton.displayName = \"IconButton\";\n\n/** The AI sparkle in the official brand gradient. Used everywhere AI appears. */\nexport function Sparkle({ className, size = 16 }: { className?: string; size?: number }) {\n const id = React.useId().replace(/:/g, \"\");\n return (\n <svg width={size} height={size} viewBox=\"2 2 16 16\" className={cn(\"shrink-0\", className)} aria-hidden>\n <defs>\n <linearGradient id={`sp${id}`} x1=\"6.89\" y1=\"4.59\" x2=\"18\" y2=\"23.67\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0.0817\" style={{ stopColor: \"var(--kit-brand-violet)\" }} />\n <stop offset=\"0.4667\" style={{ stopColor: \"var(--kit-brand-coral)\" }} />\n <stop offset=\"1\" style={{ stopColor: \"var(--kit-brand-rose)\" }} />\n </linearGradient>\n </defs>\n <path d={SPARKLE_PATH} fill={`url(#sp${id})`} />\n </svg>\n );\n}\n\n/** D15 progressive disclosure: \"+ Add a message\" / \"+ Limit to specific sites\": reveals an optional input. */\nexport function RevealLink({ className, children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n return (\n <button\n type=\"button\"\n className={cn(\n \"inline-flex h-7 items-center gap-1 rounded-kit-md text-kit-sm font-medium text-kit-fg-2 transition-colors hover:text-kit-fg [&_svg]:size-3.5\",\n className,\n )}\n {...props}\n >\n <IconPlus stroke={2} aria-hidden />\n {children}\n </button>\n );\n}\n"],"mappings":"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"}
|
package/dist/ui/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FieldDescribedByContext, FieldLabelledByContext, useFieldDescribedBy, useFieldId, useFieldLabelledBy } from "./field-context.js";
|
|
2
|
+
import { Spinner } from "./spinner.js";
|
|
2
3
|
import { Button, HintTooltip, IconButton, RevealLink, Sparkle, TooltipScope, buttonVariants, iconButtonVariants } from "./button.js";
|
|
3
4
|
import { ShortcutHint, ShortcutHintsProvider, shortcutHintsOn, useShortcutHints } from "./shortcut-hint.js";
|
|
4
5
|
import { Checkbox, ChoiceCard, ChoiceList, Progress, Radio, RadioGroup, Segmented, Slider, Switch, ToggleGroup, ToggleItem } from "./controls.js";
|
|
@@ -7,12 +8,11 @@ import { Avatar, AvatarStack, Badge, Card, CardHeader, Dot, EmptyState, Kbd, Pag
|
|
|
7
8
|
import { AGENT_PRESENCE, presenceColor, presenceSlot, registerPresenceRoster } from "./presence.js";
|
|
8
9
|
import { Field, HelpMore, Input, Label, SearchInput, Textarea } from "./input.js";
|
|
9
10
|
import { Highlight, SEARCH_THRESHOLD, Select, filterOptions, matchPositions, matchScore, nodeText, selectTriggerClass } from "./select.js";
|
|
11
|
+
import { Autocomplete } from "./autocomplete.js";
|
|
10
12
|
import { ExpandedEditorModal } from "./expanded-editor-modal.js";
|
|
11
13
|
import { ChoiceSwitch, SEGMENTED_MAX, SEGMENTED_MAX_LABEL, fitsSegmented } from "./choice-switch.js";
|
|
12
14
|
import { ActionTray, TrayMeta, TrayMore, actionTrayClass, relTime } from "./action-tray.js";
|
|
13
15
|
import { ModeBar, ModeBarDot, ModeBarLink } from "./mode-bar.js";
|
|
14
|
-
import { Spinner } from "./spinner.js";
|
|
15
16
|
import { Tab, TabList, TabPanel, Tabs } from "./tab.js";
|
|
16
17
|
import { KitToaster, kitToast, kitToastManager } from "./toast.js";
|
|
17
|
-
import { Autocomplete } from "./autocomplete.js";
|
|
18
18
|
export { AGENT_PRESENCE, ActionTray, Autocomplete, Avatar, AvatarStack, Badge, Button, Card, CardHeader, Checkbox, ChoiceCard, ChoiceList, ChoiceSwitch, Dot, EmptyState, ExpandedEditorModal, FLOATING_LAYER, Field, FieldDescribedByContext, FieldLabelledByContext, FlowBody, FlowClose, FlowFooter, FlowHeader, FlowLabel, FlowModal, HeaderStatus, HelpMore, Highlight, HintTooltip, HoverCard, HoverCardContent, HoverCardTrigger, IconButton, Input, Kbd, KitToaster, Label, Menu, MenuCheckItem, MenuContent, MenuGroup, MenuItem, MenuLabel, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSub, MenuSubContent, MenuSubTrigger, MenuTrigger, Modal, ModalBody, ModalClose, ModalContent, ModalFooter, ModalHeader, ModalTrigger, ModeBar, ModeBarDot, ModeBarLink, MoreOptions, OptionRow, PageHeader, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, Radio, RadioGroup, RevealLink, SEARCH_THRESHOLD, SEGMENTED_MAX, SEGMENTED_MAX_LABEL, SearchInput, Segmented, Select, Separator, SettingsSection, Sheet, SheetClose, SheetContent, SheetTrigger, ShortcutHint, ShortcutHintsProvider, Skeleton, Slider, Sparkle, Spinner, Stat, StatusMark, StatusPill, Switch, Tab, TabList, TabPanel, Table, Tabs, Td, Textarea, Th, ToggleGroup, ToggleItem, Tooltip, TooltipProvider, TooltipScope, Tr, TrayMeta, TrayMore, actionTrayClass, avatarRingSpread, badgeVariants, buttonVariants, colorFor, filterOptions, fitsSegmented, iconButtonVariants, initials, kitToast, kitToastManager, matchPositions, matchScore, nodeText, presenceColor, presenceSlot, registerPresenceRoster, relTime, selectTriggerClass, shortcutHintsOn, statusMap, useFieldDescribedBy, useFieldId, useFieldLabelledBy, useFocusReturn, useShortcutHints };
|
package/dist/ui/overlay.d.ts
CHANGED
|
@@ -25,7 +25,10 @@ export declare const PopoverTrigger: PopoverPrimitive.Trigger;
|
|
|
25
25
|
export declare const PopoverClose: React.ForwardRefExoticComponent<Omit<import("@base-ui/react").PopoverCloseProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
26
26
|
/** Positions the popover against this element instead of the trigger; presses on it count as inside. */
|
|
27
27
|
export declare function PopoverAnchor({ render, ...props }: useRender.ComponentProps<"div">): React.ReactElement<unknown, string | React.JSXElementConstructor<any>>;
|
|
28
|
-
export declare function PopoverContent({ className, align, side, sideOffset, keepFocus, onMouseDown, collisionBoundary, collisionPadding, collisionAvoidance, ...props }: Cls<PopoverPrimitive.Popup.Props> & {
|
|
28
|
+
export declare function PopoverContent({ className, align, side, sideOffset, keepFocus, onMouseDown, collisionBoundary, collisionPadding, collisionAvoidance, arrowPadding, ...props }: Cls<PopoverPrimitive.Popup.Props> & {
|
|
29
|
+
/** Base UI keeps a (here, missing) arrow this far from the popup's corner, sliding the popup off a tiny anchor at its
|
|
30
|
+
* edge; 0 puts the popup's edge on the anchor, as V2's Radix popover (no arrow) does */
|
|
31
|
+
arrowPadding?: number;
|
|
29
32
|
align?: "start" | "center" | "end";
|
|
30
33
|
side?: "top" | "bottom" | "left" | "right";
|
|
31
34
|
sideOffset?: number;
|
package/dist/ui/overlay.js
CHANGED
|
@@ -76,7 +76,7 @@ function PopoverAnchor({ render, ...props }) {
|
|
|
76
76
|
props
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
-
function PopoverContent({ className, align = "start", side, sideOffset = 6, keepFocus, onMouseDown, collisionBoundary, collisionPadding = 12, collisionAvoidance, ...props }) {
|
|
79
|
+
function PopoverContent({ className, align = "start", side, sideOffset = 6, keepFocus, onMouseDown, collisionBoundary, collisionPadding = 12, collisionAvoidance, arrowPadding, ...props }) {
|
|
80
80
|
const anchor = React.useContext(AnchorContext);
|
|
81
81
|
return /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, {
|
|
82
82
|
align,
|
|
@@ -85,6 +85,7 @@ function PopoverContent({ className, align = "start", side, sideOffset = 6, keep
|
|
|
85
85
|
collisionBoundary,
|
|
86
86
|
collisionPadding,
|
|
87
87
|
collisionAvoidance,
|
|
88
|
+
arrowPadding,
|
|
88
89
|
anchor: anchor?.current ? anchor : void 0,
|
|
89
90
|
className: "z-[var(--kit-z-menu,80)]",
|
|
90
91
|
...KIT_SCOPE,
|