@mindlogic-ai/logician-ui 5.0.0-alpha.0 → 5.0.0-alpha.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/MIGRATION-5.0.md +18 -18
- package/codemods/5.0/button-variants.js +1 -0
- package/codemods/5.0/codemods.test.ts +1 -0
- package/dist/components/Button/Button.legacy.d.ts.map +1 -1
- package/dist/components/Button/Button.legacy.js +1 -0
- package/dist/components/Button/Button.legacy.js.map +1 -1
- package/dist/components/Button/Button.legacy.mjs +1 -0
- package/dist/components/Button/Button.legacy.mjs.map +1 -1
- package/dist/components/Button/Button.styles.d.ts.map +1 -1
- package/dist/components/Button/Button.styles.js +18 -0
- package/dist/components/Button/Button.styles.js.map +1 -1
- package/dist/components/Button/Button.styles.mjs +18 -0
- package/dist/components/Button/Button.styles.mjs.map +1 -1
- package/dist/components/Button/Button.types.d.ts +5 -3
- package/dist/components/Button/Button.types.d.ts.map +1 -1
- package/dist/components/LineGraph/seriesColors.d.ts +2 -0
- package/dist/components/LineGraph/seriesColors.d.ts.map +1 -1
- package/dist/components/LineGraph/seriesColors.js.map +1 -1
- package/dist/components/LineGraph/seriesColors.mjs.map +1 -1
- package/dist/components/Select/Select.d.ts +5 -1
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.js +2 -2
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/Select.mjs +3 -3
- package/dist/components/Select/Select.mjs.map +1 -1
- package/dist/components/Select/Select.styles.d.ts +38 -0
- package/dist/components/Select/Select.styles.d.ts.map +1 -1
- package/dist/components/Select/Select.styles.js +35 -0
- package/dist/components/Select/Select.styles.js.map +1 -1
- package/dist/components/Select/Select.styles.mjs +35 -2
- package/dist/components/Select/Select.styles.mjs.map +1 -1
- package/dist/components/Select/Select.types.d.ts +10 -0
- package/dist/components/Select/Select.types.d.ts.map +1 -1
- package/dist/components/Select/SelectField.d.ts +1 -1
- package/dist/components/Select/SelectField.d.ts.map +1 -1
- package/dist/components/Select/SelectField.js +3 -2
- package/dist/components/Select/SelectField.js.map +1 -1
- package/dist/components/Select/SelectField.mjs +3 -2
- package/dist/components/Select/SelectField.mjs.map +1 -1
- package/dist/components/Select/index.d.ts +1 -0
- package/dist/components/Select/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Button/Button.legacy.ts +1 -0
- package/src/components/Button/Button.stories.tsx +24 -0
- package/src/components/Button/Button.styles.ts +18 -0
- package/src/components/Button/Button.types.ts +5 -2
- package/src/components/Button/Button.variants.test.tsx +9 -0
- package/src/components/LineGraph/seriesColors.ts +2 -0
- package/src/components/Select/Comparison.stories.tsx +19 -0
- package/src/components/Select/Select.stories.tsx +55 -0
- package/src/components/Select/Select.styles.ts +36 -1
- package/src/components/Select/Select.tsx +13 -3
- package/src/components/Select/Select.types.ts +11 -0
- package/src/components/Select/SelectField.tsx +7 -3
- package/src/components/Select/index.ts +1 -0
- package/src/theme/SemanticTokens.mdx +27 -1
- package/src/theme/claude.md +3 -0
- package/src/theme/contrast.test.ts +4 -3
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
export type SelectSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
3
|
+
/**
|
|
4
|
+
* `outline` (default) — the bordered trigger shared with Input and Textarea.
|
|
5
|
+
* `ghost` — no border at rest, shown on hover/focus; for dense settings rows.
|
|
6
|
+
*/
|
|
7
|
+
export type SelectVariant = 'outline' | 'ghost';
|
|
3
8
|
export interface SelectOption<T = string> {
|
|
4
9
|
label: string;
|
|
5
10
|
value: T;
|
|
@@ -42,6 +47,11 @@ interface FieldBaseProps<T> {
|
|
|
42
47
|
width?: string | number;
|
|
43
48
|
}
|
|
44
49
|
export interface SelectFieldProps<T = string> extends FieldBaseProps<T> {
|
|
50
|
+
/**
|
|
51
|
+
* Trigger look. `ghost` drops the resting border and sizes the field to its
|
|
52
|
+
* content (`width` defaults to `auto` instead of `full`).
|
|
53
|
+
*/
|
|
54
|
+
variant?: SelectVariant;
|
|
45
55
|
/**
|
|
46
56
|
* Deal the options in one after another when the list opens.
|
|
47
57
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.types.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnD,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,wCAAwC;IACxC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,iCAAiC;IACjC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACjB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IACrE;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IACvE,4DAA4D;IAC5D,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB"}
|
|
1
|
+
{"version":3,"file":"Select.types.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhD,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,wCAAwC;IACxC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,iCAAiC;IACjC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACjB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IACrE;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IACvE,4DAA4D;IAC5D,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB"}
|
|
@@ -7,5 +7,5 @@ import { SelectFieldProps } from './Select.types';
|
|
|
7
7
|
* For multi-select, grouped options or custom layouts, compose the `Select`
|
|
8
8
|
* primitives directly.
|
|
9
9
|
*/
|
|
10
|
-
export declare function SelectField<T = string>({ options, value, defaultValue, onChange, placeholder, label, ariaLabel, size, invalid, disabled, readOnly, required, name, width, stagger, }: SelectFieldProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function SelectField<T = string>({ options, value, defaultValue, onChange, placeholder, label, ariaLabel, size, invalid, disabled, readOnly, required, name, variant, width, stagger, }: SelectFieldProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
11
11
|
//# sourceMappingURL=SelectField.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectField.d.ts","sourceRoot":"","sources":["../../../src/components/Select/SelectField.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SelectField.d.ts","sourceRoot":"","sources":["../../../src/components/Select/SelectField.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,MAAM,EAAE,EACtC,OAAO,EACP,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,KAAK,EACL,SAAS,EACT,IAAW,EACX,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,OAAmB,EACnB,KAA6C,EAC7C,OAAe,GAChB,EAAE,gBAAgB,CAAC,CAAC,CAAC,2CA4ErB"}
|
|
@@ -6,6 +6,7 @@ var React = require('react');
|
|
|
6
6
|
var react = require('@chakra-ui/react');
|
|
7
7
|
var staggerProps = require('../../utils/staggerProps.js');
|
|
8
8
|
var Select = require('./Select.js');
|
|
9
|
+
var Select_styles = require('./Select.styles.js');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Single-select dropdown with the Logician design-system styling applied.
|
|
@@ -15,7 +16,7 @@ var Select = require('./Select.js');
|
|
|
15
16
|
* For multi-select, grouped options or custom layouts, compose the `Select`
|
|
16
17
|
* primitives directly.
|
|
17
18
|
*/
|
|
18
|
-
function SelectField({ options, value, defaultValue, onChange, placeholder, label, ariaLabel, size = 'md', invalid, disabled, readOnly, required, name, width = 'full', stagger = false, }) {
|
|
19
|
+
function SelectField({ options, value, defaultValue, onChange, placeholder, label, ariaLabel, size = 'md', invalid, disabled, readOnly, required, name, variant = 'outline', width = variant === 'ghost' ? 'auto' : 'full', stagger = false, }) {
|
|
19
20
|
const collection = React.useMemo(() => react.createListCollection({
|
|
20
21
|
items: options,
|
|
21
22
|
itemToValue: (item) => String(item.value),
|
|
@@ -26,7 +27,7 @@ function SelectField({ options, value, defaultValue, onChange, placeholder, labe
|
|
|
26
27
|
return (jsxRuntime.jsxs(Select.Select.Root, { collection: collection, size: size, width: width, invalid: invalid, disabled: disabled, readOnly: readOnly, required: required, name: name, value: value === undefined ? undefined : toKeys(value), defaultValue: defaultValue === undefined ? undefined : toKeys(defaultValue), onValueChange: (details) => {
|
|
27
28
|
const selected = details.items[0];
|
|
28
29
|
onChange?.(selected ? selected.value : null);
|
|
29
|
-
}, children: [jsxRuntime.jsx(Select.Select.HiddenSelect, {}), label != null ? (jsxRuntime.jsx(Select.Select.Label, { children: label })) : ((ariaLabel ?? placeholder) && (jsxRuntime.jsx(Select.Select.Label, { srOnly: true, children: ariaLabel ?? placeholder }))), jsxRuntime.jsxs(Select.Select.Control, { children: [jsxRuntime.jsx(Select.Select.Trigger, { children: jsxRuntime.jsx(Select.Select.ValueText, { placeholder: placeholder }) }), jsxRuntime.jsx(Select.Select.IndicatorGroup, { children: jsxRuntime.jsx(Select.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(Select.Select.Positioner, { children: jsxRuntime.jsx(Select.Select.Content, { children: options.map((option, index) => (jsxRuntime.jsxs(Select.Select.Item, { item: option, ...(stagger ? staggerProps.staggerProps(index) : {}), children: [jsxRuntime.jsx(Select.Select.ItemText, { children: option.label }), jsxRuntime.jsx(Select.Select.ItemIndicator, {})] }, String(option.value)))) }) }) })] }));
|
|
30
|
+
}, children: [jsxRuntime.jsx(Select.Select.HiddenSelect, {}), label != null ? (jsxRuntime.jsx(Select.Select.Label, { children: label })) : ((ariaLabel ?? placeholder) && (jsxRuntime.jsx(Select.Select.Label, { srOnly: true, children: ariaLabel ?? placeholder }))), jsxRuntime.jsxs(Select.Select.Control, { children: [jsxRuntime.jsx(Select.Select.Trigger, { variant: variant, children: jsxRuntime.jsx(Select.Select.ValueText, { placeholder: placeholder }) }), jsxRuntime.jsx(Select.Select.IndicatorGroup, { children: jsxRuntime.jsx(Select.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(Select.Select.Positioner, { children: jsxRuntime.jsx(Select.Select.Content, { css: variant === 'ghost' ? Select_styles.ghostContentStyles : undefined, children: options.map((option, index) => (jsxRuntime.jsxs(Select.Select.Item, { item: option, ...(stagger ? staggerProps.staggerProps(index) : {}), children: [jsxRuntime.jsx(Select.Select.ItemText, { children: option.label }), jsxRuntime.jsx(Select.Select.ItemIndicator, {})] }, String(option.value)))) }) }) })] }));
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
exports.SelectField = SelectField;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectField.js","sources":["../../../src/components/Select/SelectField.tsx"],"sourcesContent":[null],"names":["useMemo","createListCollection","_jsxs","Select","_jsx","Portal","staggerProps"],"mappings":"
|
|
1
|
+
{"version":3,"file":"SelectField.js","sources":["../../../src/components/Select/SelectField.tsx"],"sourcesContent":[null],"names":["useMemo","createListCollection","_jsxs","Select","_jsx","Portal","ghostContentStyles","staggerProps"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import { useMemo } from 'react';
|
|
|
4
4
|
import { createListCollection, Portal } from '@chakra-ui/react';
|
|
5
5
|
import { staggerProps } from '../../utils/staggerProps.mjs';
|
|
6
6
|
import { Select } from './Select.mjs';
|
|
7
|
+
import { ghostContentStyles } from './Select.styles.mjs';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Single-select dropdown with the Logician design-system styling applied.
|
|
@@ -13,7 +14,7 @@ import { Select } from './Select.mjs';
|
|
|
13
14
|
* For multi-select, grouped options or custom layouts, compose the `Select`
|
|
14
15
|
* primitives directly.
|
|
15
16
|
*/
|
|
16
|
-
function SelectField({ options, value, defaultValue, onChange, placeholder, label, ariaLabel, size = 'md', invalid, disabled, readOnly, required, name, width = 'full', stagger = false, }) {
|
|
17
|
+
function SelectField({ options, value, defaultValue, onChange, placeholder, label, ariaLabel, size = 'md', invalid, disabled, readOnly, required, name, variant = 'outline', width = variant === 'ghost' ? 'auto' : 'full', stagger = false, }) {
|
|
17
18
|
const collection = useMemo(() => createListCollection({
|
|
18
19
|
items: options,
|
|
19
20
|
itemToValue: (item) => String(item.value),
|
|
@@ -24,7 +25,7 @@ function SelectField({ options, value, defaultValue, onChange, placeholder, labe
|
|
|
24
25
|
return (jsxs(Select.Root, { collection: collection, size: size, width: width, invalid: invalid, disabled: disabled, readOnly: readOnly, required: required, name: name, value: value === undefined ? undefined : toKeys(value), defaultValue: defaultValue === undefined ? undefined : toKeys(defaultValue), onValueChange: (details) => {
|
|
25
26
|
const selected = details.items[0];
|
|
26
27
|
onChange?.(selected ? selected.value : null);
|
|
27
|
-
}, children: [jsx(Select.HiddenSelect, {}), label != null ? (jsx(Select.Label, { children: label })) : ((ariaLabel ?? placeholder) && (jsx(Select.Label, { srOnly: true, children: ariaLabel ?? placeholder }))), jsxs(Select.Control, { children: [jsx(Select.Trigger, { children: jsx(Select.ValueText, { placeholder: placeholder }) }), jsx(Select.IndicatorGroup, { children: jsx(Select.Indicator, {}) })] }), jsx(Portal, { children: jsx(Select.Positioner, { children: jsx(Select.Content, { children: options.map((option, index) => (jsxs(Select.Item, { item: option, ...(stagger ? staggerProps(index) : {}), children: [jsx(Select.ItemText, { children: option.label }), jsx(Select.ItemIndicator, {})] }, String(option.value)))) }) }) })] }));
|
|
28
|
+
}, children: [jsx(Select.HiddenSelect, {}), label != null ? (jsx(Select.Label, { children: label })) : ((ariaLabel ?? placeholder) && (jsx(Select.Label, { srOnly: true, children: ariaLabel ?? placeholder }))), jsxs(Select.Control, { children: [jsx(Select.Trigger, { variant: variant, children: jsx(Select.ValueText, { placeholder: placeholder }) }), jsx(Select.IndicatorGroup, { children: jsx(Select.Indicator, {}) })] }), jsx(Portal, { children: jsx(Select.Positioner, { children: jsx(Select.Content, { css: variant === 'ghost' ? ghostContentStyles : undefined, children: options.map((option, index) => (jsxs(Select.Item, { item: option, ...(stagger ? staggerProps(index) : {}), children: [jsx(Select.ItemText, { children: option.label }), jsx(Select.ItemIndicator, {})] }, String(option.value)))) }) }) })] }));
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export { SelectField };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectField.mjs","sources":["../../../src/components/Select/SelectField.tsx"],"sourcesContent":[null],"names":["_jsxs","_jsx"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"SelectField.mjs","sources":["../../../src/components/Select/SelectField.tsx"],"sourcesContent":[null],"names":["_jsxs","_jsx"],"mappings":";;;;;;;;AASA;;;;;;;AAOG;AACG,SAAU,WAAW,CAAa,EACtC,OAAO,EACP,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,KAAK,EACL,SAAS,EACT,IAAI,GAAG,IAAI,EACX,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,OAAO,GAAG,SAAS,EACnB,KAAK,GAAG,OAAO,KAAK,OAAO,GAAG,MAAM,GAAG,MAAM,EAC7C,OAAO,GAAG,KAAK,GACK,EAAA;IACpB,MAAM,UAAU,GAAG,OAAO,CACxB,MACE,oBAAoB,CAAC;AACnB,QAAA,KAAK,EAAE,OAAO;QACd,WAAW,EAAE,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;QACzC,YAAY,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK;QAClC,cAAc,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;AACjD,KAAA,CAAC,EACJ,CAAC,OAAO,CAAC,CACV;IAED,MAAM,MAAM,GAAG,CAAC,GAAyB,KACvC,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAExD,IAAA,QACEA,IAAA,CAAC,MAAM,CAAC,IAAI,IACV,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,EACtD,YAAY,EACV,YAAY,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,EAE/D,aAAa,EAAE,CAAC,OAAO,KAAI;YACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;AACjC,YAAA,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC;AAC9C,QAAA,CAAC,aAEDC,GAAA,CAAC,MAAM,CAAC,YAAY,EAAA,EAAA,CAAG,EAOtB,KAAK,IAAI,IAAI,IACZA,IAAC,MAAM,CAAC,KAAK,EAAA,EAAA,QAAA,EAAE,KAAK,EAAA,CAAgB,KAEpC,CAAC,SAAS,IAAI,WAAW,MACvBA,GAAA,CAAC,MAAM,CAAC,KAAK,IAAC,MAAM,EAAA,IAAA,EAAA,QAAA,EAAE,SAAS,IAAI,WAAW,EAAA,CAAgB,CAC/D,CACF,EACDD,IAAA,CAAC,MAAM,CAAC,OAAO,eACbC,GAAA,CAAC,MAAM,CAAC,OAAO,EAAA,EAAC,OAAO,EAAE,OAAO,YAC9BA,GAAA,CAAC,MAAM,CAAC,SAAS,EAAA,EAAC,WAAW,EAAE,WAAW,EAAA,CAAI,EAAA,CAC/B,EACjBA,GAAA,CAAC,MAAM,CAAC,cAAc,EAAA,EAAA,QAAA,EACpBA,IAAC,MAAM,CAAC,SAAS,EAAA,EAAA,CAAG,EAAA,CACE,IACT,EACjBA,GAAA,CAAC,MAAM,EAAA,EAAA,QAAA,EACLA,GAAA,CAAC,MAAM,CAAC,UAAU,EAAA,EAAA,QAAA,EAChBA,GAAA,CAAC,MAAM,CAAC,OAAO,IACb,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,kBAAkB,GAAG,SAAS,EAAA,QAAA,EAExD,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,MACzBD,IAAA,CAAC,MAAM,CAAC,IAAI,IACV,IAAI,EAAE,MAAM,EAAA,IAEP,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAA,QAAA,EAAA,CAExCC,IAAC,MAAM,CAAC,QAAQ,EAAA,EAAA,QAAA,EAAE,MAAM,CAAC,KAAK,EAAA,CAAmB,EACjDA,GAAA,CAAC,MAAM,CAAC,aAAa,KAAG,CAAA,EAAA,EAJnB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAKb,CACf,CAAC,EAAA,CACa,GACC,EAAA,CACb,CAAA,EAAA,CACG;AAElB;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Select/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,mBAAmB,gBAAgB,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Select/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,mBAAmB,gBAAgB,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"}
|
package/package.json
CHANGED
|
@@ -35,6 +35,7 @@ export const Basic: Story = {
|
|
|
35
35
|
const VARIANT_NOTES: Record<ButtonVariant, string> = {
|
|
36
36
|
primary: '화면의 주 행동 — BAZE 블랙',
|
|
37
37
|
action: '실행 버튼 — 블랙 면 + 코랄 아이콘',
|
|
38
|
+
accent: '색 있는 CTA — 코랄 면 + 검정 글씨, 화면당 하나',
|
|
38
39
|
secondary: '보조 행동 — 테두리 + 표면',
|
|
39
40
|
subtle: '낮은 강조 — 웜 워시 (기본값)',
|
|
40
41
|
ghost: '툴바·인라인 — 면/테두리 없음',
|
|
@@ -131,6 +132,29 @@ export const Action: Story = {
|
|
|
131
132
|
),
|
|
132
133
|
};
|
|
133
134
|
|
|
135
|
+
/**
|
|
136
|
+
* `accent`: the coloured call to action — coral fill (`send.bg`), dark label
|
|
137
|
+
* (`send.fg`, 6.5:1). One per view: a hero or a marketing section's main CTA.
|
|
138
|
+
* Keep `primary` (black) for product screens, where coral is reserved for
|
|
139
|
+
* run/send signals.
|
|
140
|
+
*/
|
|
141
|
+
export const Accent: Story = {
|
|
142
|
+
render: () => (
|
|
143
|
+
<HStack gap={4}>
|
|
144
|
+
<Button variant="accent" size="lg">
|
|
145
|
+
입장하기
|
|
146
|
+
</Button>
|
|
147
|
+
<Button variant="accent">시작하기</Button>
|
|
148
|
+
<Button variant="accent" size="sm">
|
|
149
|
+
시작하기
|
|
150
|
+
</Button>
|
|
151
|
+
<Button variant="accent" disabled>
|
|
152
|
+
시작하기
|
|
153
|
+
</Button>
|
|
154
|
+
</HStack>
|
|
155
|
+
),
|
|
156
|
+
};
|
|
157
|
+
|
|
134
158
|
/**
|
|
135
159
|
* All available button sizes.
|
|
136
160
|
*
|
|
@@ -116,6 +116,23 @@ export const buttonVariantStyles: Record<ButtonVariant, StyleProps> = {
|
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
118
|
},
|
|
119
|
+
/**
|
|
120
|
+
* Coral fill, dark label — the coloured CTA. Reuses the `send.*` tokens of
|
|
121
|
+
* the IconButton send button so the brand has one coral surface, and the
|
|
122
|
+
* contrast audit checks `send.fg` on all three fills as *text* (4.5:1).
|
|
123
|
+
*/
|
|
124
|
+
accent: {
|
|
125
|
+
borderColor: 'send.bg',
|
|
126
|
+
bgColor: 'send.bg',
|
|
127
|
+
color: 'send.fg',
|
|
128
|
+
_hover: { borderColor: 'send.bgHover', bgColor: 'send.bgHover' },
|
|
129
|
+
_active: {
|
|
130
|
+
borderColor: 'send.bgActive',
|
|
131
|
+
bgColor: 'send.bgActive',
|
|
132
|
+
scale: PRESSED,
|
|
133
|
+
},
|
|
134
|
+
_disabled: { ...disabledControl },
|
|
135
|
+
},
|
|
119
136
|
secondary: {
|
|
120
137
|
borderColor: 'border.default',
|
|
121
138
|
bgColor: 'bg.surface',
|
|
@@ -189,6 +206,7 @@ export const getButtonStyles = (
|
|
|
189
206
|
export const buttonVariants: ButtonVariant[] = [
|
|
190
207
|
'primary',
|
|
191
208
|
'action',
|
|
209
|
+
'accent',
|
|
192
210
|
'secondary',
|
|
193
211
|
'subtle',
|
|
194
212
|
'ghost',
|
|
@@ -16,6 +16,8 @@ import type { PolymorphicProps } from '../../types/polymorphic';
|
|
|
16
16
|
*
|
|
17
17
|
* - `primary`: BAZE black fill — the one main action of a screen
|
|
18
18
|
* - `action`: black "run" button whose icon is coral (`action.iconOnSolid`)
|
|
19
|
+
* - `accent`: coral fill with dark text (`send.*`) — the one coloured call to
|
|
20
|
+
* action of a marketing surface or a hero; at most one per view
|
|
19
21
|
* - `secondary`: outlined surface button — cancel, alternative actions
|
|
20
22
|
* - `subtle`: quiet warm-wash fill — low-emphasis actions (the default)
|
|
21
23
|
* - `ghost`: no fill, no border — toolbars, inline actions
|
|
@@ -27,6 +29,7 @@ import type { PolymorphicProps } from '../../types/polymorphic';
|
|
|
27
29
|
export type ButtonVariant =
|
|
28
30
|
| 'primary'
|
|
29
31
|
| 'action'
|
|
32
|
+
| 'accent'
|
|
30
33
|
| 'secondary'
|
|
31
34
|
| 'subtle'
|
|
32
35
|
| 'ghost'
|
|
@@ -83,8 +86,8 @@ export type ButtonOwnProps = Omit<
|
|
|
83
86
|
/**
|
|
84
87
|
* Danger-coloured text (`danger.fg`) for a destructive action that does not
|
|
85
88
|
* deserve a red fill. Applies to `secondary`, `ghost` and `subtle`; the
|
|
86
|
-
* border and fill stay the variant's own. Ignored on `primary`, `action
|
|
87
|
-
* `danger` (use `variant="danger"` for a filled destructive button).
|
|
89
|
+
* border and fill stay the variant's own. Ignored on `primary`, `action`,
|
|
90
|
+
* `accent` and `danger` (use `variant="danger"` for a filled destructive button).
|
|
88
91
|
* @default false
|
|
89
92
|
*/
|
|
90
93
|
isDestructive?: boolean;
|
|
@@ -47,6 +47,15 @@ describe('Button variants (5.0)', () => {
|
|
|
47
47
|
expect(getButtonStyles('danger', true).color).toBe('danger.onSolid');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
it('accent is the coral fill with a dark label (send.* tokens)', () => {
|
|
51
|
+
const accent = getButtonStyles('accent');
|
|
52
|
+
expect(accent.bgColor).toBe('send.bg');
|
|
53
|
+
expect(accent.color).toBe('send.fg');
|
|
54
|
+
expect(accent._hover).toMatchObject({ bgColor: 'send.bgHover' });
|
|
55
|
+
expect(accent._active).toMatchObject({ bgColor: 'send.bgActive' });
|
|
56
|
+
expect(getButtonStyles('accent', true).color).toBe('send.fg');
|
|
57
|
+
});
|
|
58
|
+
|
|
50
59
|
it('disabled is a fixed colour pair, not opacity', () => {
|
|
51
60
|
expect(buttonVariantStyles.primary._disabled).toMatchObject({
|
|
52
61
|
bg: 'disabled.bg',
|
|
@@ -8,6 +8,8 @@ import { SYSTEM_HUES } from '@/theme/palette.generated';
|
|
|
8
8
|
* that carry no meaning in the system. Red, yellow and green are the danger,
|
|
9
9
|
* warning and success colours, and blue is info and links, so none of them
|
|
10
10
|
* may colour a series by default — a red "UV" line reads as an error.
|
|
11
|
+
* (Blue is reserved for data as a whole: a single-measure chart is a blue
|
|
12
|
+
* monochrome ramp — SemanticTokens.mdx, "Blue means data and links".)
|
|
11
13
|
*
|
|
12
14
|
* Every stroke clears 3:1 against the page in both modes. Cyan, mint and
|
|
13
15
|
* orange are too light at their base value in light mode (≈1.9:1), so they
|
|
@@ -54,6 +54,25 @@ export const SideBySide = () => (
|
|
|
54
54
|
<SelectField options={categoryOptions} placeholder="Select" invalid />
|
|
55
55
|
<Textarea placeholder="Textarea" invalid />
|
|
56
56
|
</Row>
|
|
57
|
+
<Row label='Select variant="ghost" — default / invalid / disabled'>
|
|
58
|
+
<SelectField
|
|
59
|
+
variant="ghost"
|
|
60
|
+
options={categoryOptions}
|
|
61
|
+
placeholder="Select"
|
|
62
|
+
/>
|
|
63
|
+
<SelectField
|
|
64
|
+
variant="ghost"
|
|
65
|
+
options={categoryOptions}
|
|
66
|
+
placeholder="Select"
|
|
67
|
+
invalid
|
|
68
|
+
/>
|
|
69
|
+
<SelectField
|
|
70
|
+
variant="ghost"
|
|
71
|
+
options={categoryOptions}
|
|
72
|
+
placeholder="Select"
|
|
73
|
+
disabled
|
|
74
|
+
/>
|
|
75
|
+
</Row>
|
|
57
76
|
<Row label="Disabled">
|
|
58
77
|
<Input placeholder="Input" disabled />
|
|
59
78
|
<SelectField options={categoryOptions} placeholder="Select" disabled />
|
|
@@ -2,10 +2,12 @@ import { useState } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
4
|
createListCollection,
|
|
5
|
+
Flex,
|
|
5
6
|
HStack,
|
|
6
7
|
Portal,
|
|
7
8
|
Span,
|
|
8
9
|
Stack,
|
|
10
|
+
Text,
|
|
9
11
|
} from '@chakra-ui/react';
|
|
10
12
|
import { Meta } from '@storybook/react';
|
|
11
13
|
|
|
@@ -78,6 +80,59 @@ export const Disabled = () => (
|
|
|
78
80
|
</Box>
|
|
79
81
|
);
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* `variant="ghost"` — no resting border, sized to its content. For dense
|
|
85
|
+
* settings rows where a column of bordered triggers outweighs the card.
|
|
86
|
+
* Hover shows the control border.
|
|
87
|
+
*/
|
|
88
|
+
export const Ghost = () => {
|
|
89
|
+
const rows = [
|
|
90
|
+
['크레딧 한도 변경', '관리자가 내 크레딧 한도를 바꾸면 알려드립니다.'],
|
|
91
|
+
['크레딧 잔량 부족', '내 크레딧이 얼마 남지 않으면 알려드립니다.'],
|
|
92
|
+
['결제 실패', '결제에 실패하거나 자동으로 환불되면 알려드립니다.'],
|
|
93
|
+
];
|
|
94
|
+
const channelOptions = [
|
|
95
|
+
{ label: '앱 알림', value: 'app' },
|
|
96
|
+
{ label: '앱 알림 + 이메일', value: 'app_email' },
|
|
97
|
+
{ label: '끄기', value: 'off' },
|
|
98
|
+
];
|
|
99
|
+
return (
|
|
100
|
+
<Box
|
|
101
|
+
maxW="640px"
|
|
102
|
+
borderWidth="1px"
|
|
103
|
+
borderColor="border.subtle"
|
|
104
|
+
borderRadius="12px"
|
|
105
|
+
>
|
|
106
|
+
{rows.map(([title, description], index) => (
|
|
107
|
+
<Flex
|
|
108
|
+
key={title}
|
|
109
|
+
align="center"
|
|
110
|
+
justify="space-between"
|
|
111
|
+
gap={4}
|
|
112
|
+
px={4}
|
|
113
|
+
py={3}
|
|
114
|
+
borderTopWidth={index === 0 ? 0 : '1px'}
|
|
115
|
+
borderColor="border.subtle"
|
|
116
|
+
>
|
|
117
|
+
<Box>
|
|
118
|
+
<Text fontWeight="semibold">{title}</Text>
|
|
119
|
+
<Text color="fg.muted" fontSize="sm">
|
|
120
|
+
{description}
|
|
121
|
+
</Text>
|
|
122
|
+
</Box>
|
|
123
|
+
<SelectField
|
|
124
|
+
variant="ghost"
|
|
125
|
+
size="sm"
|
|
126
|
+
ariaLabel={title}
|
|
127
|
+
options={channelOptions}
|
|
128
|
+
defaultValue="app"
|
|
129
|
+
/>
|
|
130
|
+
</Flex>
|
|
131
|
+
))}
|
|
132
|
+
</Box>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
81
136
|
export const ManyOptions = () => {
|
|
82
137
|
const manyOptions = Array.from({ length: 100 }, (_, i) => ({
|
|
83
138
|
label: `Option ${i + 1}`,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* update these values to match.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { disabledControl } from '@/utils/disabledControl';
|
|
11
|
+
import { disabledControl, disabledText } from '@/utils/disabledControl';
|
|
12
12
|
|
|
13
13
|
const focusVars = {
|
|
14
14
|
'--focus-color': 'var(--chakra-colors-primary-main)',
|
|
@@ -47,6 +47,31 @@ export const triggerStyles = {
|
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* `variant="ghost"` delta on top of `triggerStyles`, for dense rows (settings
|
|
52
|
+
* lists) where a column of bordered triggers outweighs the card around them.
|
|
53
|
+
* No border at rest; hover brings back `border.control`, focus/open keep the
|
|
54
|
+
* normal ring, and an invalid field keeps its red border at rest so an error
|
|
55
|
+
* is never hidden. The chevron identifies the control (`fg.muted`, well over
|
|
56
|
+
* the 3:1 non-text floor), which is what lets the resting border go.
|
|
57
|
+
*/
|
|
58
|
+
export const ghostTriggerStyles = {
|
|
59
|
+
bg: 'transparent',
|
|
60
|
+
borderColor: 'transparent',
|
|
61
|
+
// Content-sized: Chakra caps the value at `maxW: 80%` of the trigger and
|
|
62
|
+
// overlays the chevron, which is circular once the trigger sizes to the
|
|
63
|
+
// value — it truncates to "앱 …". Lift the cap and reserve the chevron's
|
|
64
|
+
// room (indicator group = icon + padding on both sides) instead.
|
|
65
|
+
pe: 'calc(var(--select-trigger-padding-x) * 2 + 1rem)',
|
|
66
|
+
'& [data-part=value-text]': { maxW: 'none', whiteSpace: 'nowrap' },
|
|
67
|
+
_hover: { borderColor: 'border.control' },
|
|
68
|
+
_disabled: {
|
|
69
|
+
...disabledText,
|
|
70
|
+
cursor: 'not-allowed',
|
|
71
|
+
fontWeight: 'semibold',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
50
75
|
/** Combobox text input — the typeable control. */
|
|
51
76
|
export const inputStyles = {
|
|
52
77
|
...fieldBase,
|
|
@@ -62,6 +87,16 @@ export const contentStyles = {
|
|
|
62
87
|
padding: '4px',
|
|
63
88
|
};
|
|
64
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Dropdown panel for a ghost trigger — at least as wide as the trigger, but
|
|
92
|
+
* grown to the longest option, since a content-sized trigger is often
|
|
93
|
+
* narrower than the options it opens.
|
|
94
|
+
*/
|
|
95
|
+
export const ghostContentStyles = {
|
|
96
|
+
width: 'max-content',
|
|
97
|
+
minW: 'var(--reference-width)',
|
|
98
|
+
};
|
|
99
|
+
|
|
65
100
|
/** Individual option for both Select and Combobox. */
|
|
66
101
|
export const itemStyles = {
|
|
67
102
|
borderRadius: '4px',
|
|
@@ -5,18 +5,28 @@ import { mergeCss } from '@/utils/mergeCss';
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
contentStyles,
|
|
8
|
+
ghostTriggerStyles,
|
|
8
9
|
indicatorStyles,
|
|
9
10
|
itemStyles,
|
|
10
11
|
triggerStyles,
|
|
11
12
|
} from './Select.styles';
|
|
13
|
+
import type { SelectVariant } from './Select.types';
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
export interface SelectTriggerProps extends ChakraSelect.TriggerProps {
|
|
16
|
+
variant?: SelectVariant;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Trigger = forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
20
|
+
function SelectTrigger({ css, variant = 'outline', ...props }, ref) {
|
|
15
21
|
return (
|
|
16
22
|
<ChakraSelect.Trigger
|
|
17
23
|
ref={ref}
|
|
18
24
|
{...props}
|
|
19
|
-
css={mergeCss(
|
|
25
|
+
css={mergeCss(
|
|
26
|
+
triggerStyles,
|
|
27
|
+
variant === 'ghost' && ghostTriggerStyles,
|
|
28
|
+
css
|
|
29
|
+
)}
|
|
20
30
|
/>
|
|
21
31
|
);
|
|
22
32
|
}
|
|
@@ -2,6 +2,12 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
|
|
3
3
|
export type SelectSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* `outline` (default) — the bordered trigger shared with Input and Textarea.
|
|
7
|
+
* `ghost` — no border at rest, shown on hover/focus; for dense settings rows.
|
|
8
|
+
*/
|
|
9
|
+
export type SelectVariant = 'outline' | 'ghost';
|
|
10
|
+
|
|
5
11
|
export interface SelectOption<T = string> {
|
|
6
12
|
label: string;
|
|
7
13
|
value: T;
|
|
@@ -46,6 +52,11 @@ interface FieldBaseProps<T> {
|
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export interface SelectFieldProps<T = string> extends FieldBaseProps<T> {
|
|
55
|
+
/**
|
|
56
|
+
* Trigger look. `ghost` drops the resting border and sizes the field to its
|
|
57
|
+
* content (`width` defaults to `auto` instead of `full`).
|
|
58
|
+
*/
|
|
59
|
+
variant?: SelectVariant;
|
|
49
60
|
/**
|
|
50
61
|
* Deal the options in one after another when the list opens.
|
|
51
62
|
*
|
|
@@ -4,6 +4,7 @@ import { createListCollection, Portal } from '@chakra-ui/react';
|
|
|
4
4
|
import { staggerProps } from '@/utils/staggerProps';
|
|
5
5
|
|
|
6
6
|
import { Select } from './Select';
|
|
7
|
+
import { ghostContentStyles } from './Select.styles';
|
|
7
8
|
import { SelectFieldProps } from './Select.types';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -28,7 +29,8 @@ export function SelectField<T = string>({
|
|
|
28
29
|
readOnly,
|
|
29
30
|
required,
|
|
30
31
|
name,
|
|
31
|
-
|
|
32
|
+
variant = 'outline',
|
|
33
|
+
width = variant === 'ghost' ? 'auto' : 'full',
|
|
32
34
|
stagger = false,
|
|
33
35
|
}: SelectFieldProps<T>) {
|
|
34
36
|
const collection = useMemo(
|
|
@@ -79,7 +81,7 @@ export function SelectField<T = string>({
|
|
|
79
81
|
)
|
|
80
82
|
)}
|
|
81
83
|
<Select.Control>
|
|
82
|
-
<Select.Trigger>
|
|
84
|
+
<Select.Trigger variant={variant}>
|
|
83
85
|
<Select.ValueText placeholder={placeholder} />
|
|
84
86
|
</Select.Trigger>
|
|
85
87
|
<Select.IndicatorGroup>
|
|
@@ -88,7 +90,9 @@ export function SelectField<T = string>({
|
|
|
88
90
|
</Select.Control>
|
|
89
91
|
<Portal>
|
|
90
92
|
<Select.Positioner>
|
|
91
|
-
<Select.Content
|
|
93
|
+
<Select.Content
|
|
94
|
+
css={variant === 'ghost' ? ghostContentStyles : undefined}
|
|
95
|
+
>
|
|
92
96
|
{options.map((option, index) => (
|
|
93
97
|
<Select.Item
|
|
94
98
|
item={option}
|
|
@@ -40,7 +40,7 @@ createLogicianTheme({ neutral: 'slate' }) // cool gray — app code unchanged
|
|
|
40
40
|
| Beige accent: text, icons, borders on those (`secondary` identity) | `secondary.fg` / `.fgStrong` / `.border` |
|
|
41
41
|
| Beige tint (tint/accent only — **not a component background**: reads peach as a fill) | `secondary.surface` / `.surfaceStrong` |
|
|
42
42
|
| Run button (black + coral icon) · small coral marks | `action.bg` / `.fg` / `.iconOnSolid` · `action.mark` |
|
|
43
|
-
| Send button | `send.bg` / `.bgHover` / `.bgActive` / `.fg` |
|
|
43
|
+
| Send button, Button `accent` | `send.bg` / `.bgHover` / `.bgActive` / `.fg` |
|
|
44
44
|
| Unread count | `count.bg` / `count.fg` |
|
|
45
45
|
| Disabled (no opacity) | `disabled.bg` / `disabled.fg` |
|
|
46
46
|
| Status | `danger` · `success` · `warning` · `info` → `.bg` `.border` `.fg` `.solid` `.onSolid` |
|
|
@@ -50,6 +50,32 @@ Emphasis is weight, not colour: headings use `fg.default`, never `primary.*`.
|
|
|
50
50
|
Status colours mean status — don't borrow `success`/`warning` for decoration;
|
|
51
51
|
that is what `category.*` is for.
|
|
52
52
|
|
|
53
|
+
## Blue means data and links
|
|
54
|
+
|
|
55
|
+
Blue is the one hue BAZE gives a meaning outside status: **data and links**.
|
|
56
|
+
Use it for both, and for nothing decorative.
|
|
57
|
+
|
|
58
|
+
- **Links** — `fg.link` / `fg.linkHover` (blue ink, #48607A / #AFC7E1), in chat
|
|
59
|
+
messages, markdown and app copy alike. Never ink-black or coral for a link.
|
|
60
|
+
- **Data visualisation** — usage/share bars, single-measure bar and line charts,
|
|
61
|
+
maps, heatmaps, progress-by-share: a **blue monochrome ramp**, never a
|
|
62
|
+
rainbow and never a grey ramp. Steps differ in lightness only (the saturated
|
|
63
|
+
`blue.base` is left out); the largest value gets the strongest step.
|
|
64
|
+
|
|
65
|
+
| Step | Light | Dark |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| 1 (largest) | `#48607A` (`blue.ink`) · white label | `#AFC7E1` (`blue.inkDark`) · ink label |
|
|
68
|
+
| 2 | `#7492B3` · ink label | `#7492B3` · ink label |
|
|
69
|
+
| 3 | `#AFC7E1` · ink label | `#48607A` · white label |
|
|
70
|
+
| 4 | `#DFEBF9` (`blue.surface`) · ink label | `#232F3D` (`blue.surfaceDark`) · white label |
|
|
71
|
+
|
|
72
|
+
Pick each label by measured contrast (≥ 4.5:1), not by step number.
|
|
73
|
+
A chart that must tell several *categories* apart (multi-series lines) keeps
|
|
74
|
+
`CHART_SERIES_HUES` in `LineGraph/seriesColors.ts`, which leaves blue out so
|
|
75
|
+
no series reads as a link or an info state.
|
|
76
|
+
`info` is also blue; status always comes with an icon and words, so a blue
|
|
77
|
+
chart is never mistaken for an info notice.
|
|
78
|
+
|
|
53
79
|
## Modes
|
|
54
80
|
|
|
55
81
|
Every role flips with the colour mode (`_light` / `_dark`) and has a
|
package/src/theme/claude.md
CHANGED
|
@@ -249,6 +249,9 @@ follow the scheme and high contrast. Base font size is 14px.
|
|
|
249
249
|
4. Beige (`secondary.surface*`) is for small surfaces (bubbles, soft
|
|
250
250
|
badges/tags, the modal header band), never rows or panels.
|
|
251
251
|
5. Disabled = `disabled.*`, not opacity. Focus = `focusRing`.
|
|
252
|
+
6. Blue means data and links: links are `fg.link`; data visualisation
|
|
253
|
+
(usage bars, single-measure charts, maps) is a blue monochrome ramp, never a
|
|
254
|
+
rainbow or grey ramp. Values: SemanticTokens.mdx → "Blue means data and links".
|
|
252
255
|
|
|
253
256
|
## Extending the Theme
|
|
254
257
|
|
|
@@ -77,9 +77,10 @@ const PAIRS: [string, string, Kind][] = [
|
|
|
77
77
|
['action-iconOnSolid', 'action-bgHover', 'nontext'],
|
|
78
78
|
['action-fg', 'action-bgActive', 'text'],
|
|
79
79
|
['action-iconOnSolid', 'action-bgActive', 'nontext'],
|
|
80
|
-
|
|
81
|
-
['send-fg', 'send-
|
|
82
|
-
['send-fg', 'send-
|
|
80
|
+
// `text`, not `nontext`: Button `accent` puts a label on these fills.
|
|
81
|
+
['send-fg', 'send-bg', 'text'],
|
|
82
|
+
['send-fg', 'send-bgHover', 'text'],
|
|
83
|
+
['send-fg', 'send-bgActive', 'text'],
|
|
83
84
|
['danger-fg', 'danger-bg', 'text'],
|
|
84
85
|
['danger-onSolid', 'danger-solid', 'text'],
|
|
85
86
|
['success-fg', 'success-bg', 'text'],
|