🐛 Fix numeric input errors detected on review (#7427)

* 🐛 Fix review errors

* 📚 Add docs for numeric input component

* 🐛 Sort tokens alphabetically
This commit is contained in:
Eva Marco 2025-10-03 10:14:25 +02:00 committed by GitHub
parent 93d4b19477
commit a4f20564af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 133 additions and 20 deletions

View File

@ -566,13 +566,13 @@
(watch [_ _ _]
(let [{:keys [attributes all-attributes on-update-shape]}
(get token-properties (:type token))
unapply-tokens?
(cft/shapes-token-applied? token shapes (or all-attributes attributes))
unapply-tokens?
(cft/shapes-token-applied? token shapes (or attrs all-attributes attributes))
shape-ids (map :id shapes)]
(if unapply-tokens?
(rx/of
(unapply-token {:attributes (or all-attributes attributes)
(unapply-token {:attributes (or attrs all-attributes attributes)
:token token
:shape-ids shape-ids}))
(rx/of

View File

@ -44,8 +44,9 @@ export default {
property: "search",
},
parameters: {
controls: { exclude: ["tokens"] },
controls: { exclude: ["tokens"] }
},
render: ({ ...args }) => <NumericInput {...args} />,
};
@ -116,6 +117,14 @@ export const WithTokens = {
},
],
},
},
parameters: {
controls: { exclude: ["tokens"] },
docs: {
story: {
height: "320px",
},
},
},
render: ({ ...args }) => <NumericInput {...args} />,
};

View File

@ -149,6 +149,32 @@
j)))
indices)))
(defn- sort-groups-and-tokens
"Sorts both the groups and the tokens inside them alphabetically.
Input:
A map where:
- keys are groups (keywords or strings, e.g. :dimensions, :colors)
- values are vectors of token maps, each containing at least a :name key
Example input:
{:dimensions [{:name \"tres\"} {:name \"quini\"}]
:colors [{:name \"azul\"} {:name \"rojo\"}]}
Output:
A sorted map where:
- groups are ordered alphabetically by key
- tokens inside each group are sorted alphabetically by :name
Example output:
{:colors [{:name \"azul\"} {:name \"rojo\"}]
:dimensions [{:name \"quini\"} {:name \"tres\"}]}"
[groups->tokens]
(into (sorted-map) ;; ensure groups are ordered alphabetically by their key
(for [[group tokens] groups->tokens]
[group (sort-by :name tokens)])))
(def ^:private schema:icon
[:and :string [:fn #(contains? icon-list %)]])
@ -260,11 +286,13 @@
(mf/with-memo [tokens filter-id]
(delay
(let [tokens (if (delay? tokens) @tokens tokens)
sorted-tokens (sort-groups-and-tokens tokens)
partial (extract-partial-brace-text filter-id)
options (if (seq partial)
(filter-token-groups-by-name tokens partial)
tokens)
no-sets? (nil? tokens)]
(filter-token-groups-by-name sorted-tokens partial)
sorted-tokens)
no-sets? (nil? sorted-tokens)]
(generate-dropdown-options options no-sets?))))
selected-id*
@ -601,6 +629,7 @@
{:content property
:id property}
[:> icon* {:icon-id icon
:size "s"
:aria-labelledby property
:class (stl/css :icon)}]]))
:slot-end (when-not disabled
@ -634,6 +663,7 @@
{:content property
:id property}
[:> icon* {:icon-id icon
:size "s"
:aria-labelledby property
:class (stl/css :icon)}]]))
:token-wrapper-ref token-wrapper-ref

View File

@ -4,8 +4,79 @@
Copyright (c) KALEIDOS INC */ }
import { Canvas, Meta } from '@storybook/blocks';
import * as InputStories from "./numeric_input.stories";
import * as InputStories from "./numeric-input.stories";
<Meta title="Controls/Numeric Input" />
# Numeric Input
# Numeric Input
The `numeric-input*` component allows users to enter numerical values, apply tokens, or perform simple mathematical expressions.
It combines a numeric input field with token integration, making it flexible for design systems where values can come from either fixed numbers or token references.
## Variants
### Default
The standard numeric input that accepts direct numeric input.
<Canvas of={InputStories.Default} />
### With Tokens
When tokens are available, the user can open a dropdown and apply a token instead of entering a fixed number.
The token will be displayed inside the input field and can be detached if needed.
<Canvas of={InputStories.WithTokens} />
## Technical notes
### Tokens
- Numeric input supports applying tokens via a dropdown that opens when typing `{` or clicking the token button.
- Tokens are grouped and searchable.
- Once a token is applied, the input field is replaced with a **token field**, showing the token name and its resolved value.
### Validation & Math Expressions
- The input accepts simple math expressions (e.g. `2+2`, `100/4`) and resolves them on blur.
- Values are automatically clamped to the provided `min` and `max` range.
- If the input is left empty and `nillable` is enabled, the value can be `nil`.
### Icons
`numeric-input*` supports optional icons at the start of the field for additional context.
Icons come from the `app.main.ui.ds.foundations.assets.icon` namespace.
```clj
(ns app.main.ui.foo
(:require
[app.main.ui.ds.foundations.assets.icon :as i]))
[:> numeric-input*
{:icon i/hash
:placeholder "Enter number"
:min 0
:max 100
:step 1}]
```
## Usage guidelines (design)
### Where to Use
Use numeric input in forms and properties panels where users need to adjust numerical values, such as sizes, spacings, or opacities.
### When to Use
When users must provide a numeric value within a defined range.
When design tokens can be applied instead of raw numbers.
When supporting advanced workflows where math expressions improve speed.
### Interaction / Behavior
Typing Numbers: The user can type numbers directly in the field.
Using Tokens: Typing `{` opens a token dropdown; selecting one replaces the raw value with the token.
Increment / Decrement: Users can adjust values with arrow keys ↑/↓ or mouse wheel.
Validation: Invalid or out-of-range inputs fall back to the last valid value or the default.
Detaching Tokens: Applied tokens can be removed via the detach button or with Backspace/Delete.

View File

@ -7,9 +7,12 @@
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/typography.scss" as t;
.input-wrapper {
--input-padding-size: var(--sp-xs);
--opacity-button: 0;
@include t.use-typography("code-font");
display: flex;
flex-direction: column;
gap: var(--sp-xs);

View File

@ -6,7 +6,6 @@
(ns app.main.ui.ds.controls.utilities.input-field
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[app.common.data :as d]
@ -58,6 +57,14 @@
:type (d/nilv type "text")
:id id
:max-length (d/nilv max-length max-input-length)})
inside-class (stl/css-case :input-wrapper true
:has-hint has-hint
:hint-type-hint (= hint-type "hint")
:hint-type-warning (= hint-type "warning")
:hint-type-error (= hint-type "error")
:variant-seamless (= variant "seamless")
:variant-dense (= variant "dense")
:variant-comfortable (= variant "comfortable"))
on-icon-click
(mf/use-fn
(mf/deps ref)
@ -66,14 +73,7 @@
(dom/select-node input-node)
(dom/focus! input-node))))]
[:div {:class (dm/str class " " (stl/css-case :input-wrapper true
:has-hint has-hint
:hint-type-hint (= hint-type "hint")
:hint-type-warning (= hint-type "warning")
:hint-type-error (= hint-type "error")
:variant-seamless (= variant "seamless")
:variant-dense (= variant "dense")
:variant-comfortable (= variant "comfortable")))}
[:div {:class [inside-class class]}
(when (some? slot-start)
slot-start)
(when (some? icon)

View File

@ -25,7 +25,7 @@
background: var(--input-bg-color);
border-radius: $br-8;
padding: 0 var(--sp-s);
padding: 0 var(--input-padding-size, var(--sp-s));
outline: $b-1 solid var(--input-outline-color);
&:hover {

View File

@ -25,7 +25,7 @@
inline-size: 100%;
background: var(--token-field-bg-color);
border-radius: $br-8;
padding: var(--sp-xs) var(--sp-s);
padding: var(--sp-xs);
outline: $b-1 solid var(--token-field-outline-color);
&:hover {