diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs
index bc47fb8c21..47caaaf64c 100644
--- a/frontend/src/app/main/data/workspace/tokens/application.cljs
+++ b/frontend/src/app/main/data/workspace/tokens/application.cljs
@@ -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
diff --git a/frontend/src/app/main/ui/ds/controls/numeric_input.stories.jsx b/frontend/src/app/main/ui/ds/controls/numeric-input.stories.jsx
similarity index 95%
rename from frontend/src/app/main/ui/ds/controls/numeric_input.stories.jsx
rename to frontend/src/app/main/ui/ds/controls/numeric-input.stories.jsx
index f3d2d20c8c..43d2971c42 100644
--- a/frontend/src/app/main/ui/ds/controls/numeric_input.stories.jsx
+++ b/frontend/src/app/main/ui/ds/controls/numeric-input.stories.jsx
@@ -44,8 +44,9 @@ export default {
property: "search",
},
parameters: {
- controls: { exclude: ["tokens"] },
+ controls: { exclude: ["tokens"] }
},
+
render: ({ ...args }) => ,
};
@@ -116,6 +117,14 @@ export const WithTokens = {
},
],
},
+ },
+ parameters: {
+ controls: { exclude: ["tokens"] },
+ docs: {
+ story: {
+ height: "320px",
+ },
+ },
},
render: ({ ...args }) => ,
};
diff --git a/frontend/src/app/main/ui/ds/controls/numeric_input.cljs b/frontend/src/app/main/ui/ds/controls/numeric_input.cljs
index 1267c8caf6..ed11c7b278 100644
--- a/frontend/src/app/main/ui/ds/controls/numeric_input.cljs
+++ b/frontend/src/app/main/ui/ds/controls/numeric_input.cljs
@@ -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
diff --git a/frontend/src/app/main/ui/ds/controls/numeric_input.mdx b/frontend/src/app/main/ui/ds/controls/numeric_input.mdx
index 9eaac5dd46..def6d701d4 100644
--- a/frontend/src/app/main/ui/ds/controls/numeric_input.mdx
+++ b/frontend/src/app/main/ui/ds/controls/numeric_input.mdx
@@ -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";
-# Numeric Input
\ No newline at end of file
+# 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.
+
+
+### 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.
+
+
+## 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.
diff --git a/frontend/src/app/main/ui/ds/controls/numeric_input.scss b/frontend/src/app/main/ui/ds/controls/numeric_input.scss
index a30b94d82b..ec9b2aec69 100644
--- a/frontend/src/app/main/ui/ds/controls/numeric_input.scss
+++ b/frontend/src/app/main/ui/ds/controls/numeric_input.scss
@@ -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);
diff --git a/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs b/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs
index 23ca7eab25..4a8bcb1554 100644
--- a/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs
+++ b/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs
@@ -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)
diff --git a/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss b/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss
index 08d00e7807..3e95b194d9 100644
--- a/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss
+++ b/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss
@@ -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 {
diff --git a/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss b/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss
index 07a60ccb05..37498bdf7e 100644
--- a/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss
+++ b/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss
@@ -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 {