srcdev-nuxt-components 9.4.10 → 9.4.12
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/.claude/commands/migrate-component.md +18 -0
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/output.html +1 -1
- package/.claude/skills/components/input-number-core.md +214 -0
- package/.claude/skills/components/input-select-core.md +151 -0
- package/.claude/skills/components/input-text-core.md +16 -0
- package/.claude/skills/components/input-textarea-core.md +154 -0
- package/.claude/skills/components/select-menu.md +195 -0
- package/.claude/skills/components/toggle-switch-core.md +150 -0
- package/.claude/skills/index.md +5 -0
- package/.vscode/srcdev-component-input-number.code-snippets +65 -0
- package/.vscode/srcdev-component-input-select.code-snippets +42 -0
- package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
- package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
- package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/section-parallax/SectionParallax.vue +1 -1
- package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
- package/app/components/01.atoms/card/CardCore.vue +4 -4
- package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +2 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +1 -1
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
- package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
- package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +11 -0
- package/app/components/02.molecules/pricing-card/PricingCard.vue +3 -1
- package/app/components/02.molecules/pricing-card/stories/PricingCard.stories.ts +2 -0
- package/app/components/02.molecules/pricing-card/tests/PricingCard.spec.ts +13 -0
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
- package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +3 -3
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +3 -3
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +18 -18
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
- package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
- package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
- package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
- package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
- package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
- package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
- package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-range/InputRangeCore.vue +2 -2
- package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
- package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
- package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
- package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
- package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
- package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +95 -10
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +4 -4
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +6 -6
- package/app/types/components/index.ts +1 -0
- package/app/types/components/select-menu.d.ts +5 -0
- package/package.json +1 -1
- package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts
CHANGED
|
@@ -259,7 +259,7 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
259
259
|
.carousel-flip-demo.carousel-flip .item-container {
|
|
260
260
|
max-inline-size: 800px;
|
|
261
261
|
margin-inline: auto;
|
|
262
|
-
outline: 1px solid
|
|
262
|
+
outline: 1px solid var(--slate-08, #666);
|
|
263
263
|
padding-block: 6px;
|
|
264
264
|
padding-inline: 6px;
|
|
265
265
|
}
|
|
@@ -272,12 +272,12 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
.carousel-flip-demo.carousel-flip .item {
|
|
275
|
-
background-color:
|
|
275
|
+
background-color: var(--slate-05, #e5e5e5);
|
|
276
276
|
contain: layout style paint;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
.carousel-flip-demo.carousel-flip .item:nth-child(odd) {
|
|
280
|
-
background-color:
|
|
280
|
+
background-color: var(--slate-06, #999);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
.custom-carousel-item {
|
|
@@ -287,11 +287,11 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
287
287
|
justify-content: center;
|
|
288
288
|
aspect-ratio: 4 / 3;
|
|
289
289
|
inline-size: 100%;
|
|
290
|
-
color:
|
|
290
|
+
color: #aaa;
|
|
291
291
|
padding-block: 10px;
|
|
292
292
|
padding-inline: 10px;
|
|
293
293
|
border-radius: 4px;
|
|
294
|
-
outline: 1px solid
|
|
294
|
+
outline: 1px solid #00000090;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
.carousel-flip-demo.carousel-flip .controls-container {
|
|
@@ -323,7 +323,7 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
323
323
|
width: var(--marker-width);
|
|
324
324
|
height: var(--marker-height);
|
|
325
325
|
border-radius: var(--marker-border-radius);
|
|
326
|
-
background-color:
|
|
326
|
+
background-color: var(--slate-05, #e5e5e5);
|
|
327
327
|
line-height: 3px;
|
|
328
328
|
transition: all 0.3s linear;
|
|
329
329
|
border: 1px solid transparent;
|
|
@@ -331,13 +331,13 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
.carousel-flip-demo.carousel-flip .btn-marker.active {
|
|
334
|
-
background-color:
|
|
334
|
+
background-color: var(--slate-012, #000);
|
|
335
335
|
will-change: background-color;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
.carousel-flip-demo.carousel-flip .btn-marker:hover,
|
|
339
339
|
.carousel-flip-demo.carousel-flip .btn-marker:focus-visible {
|
|
340
|
-
outline-color:
|
|
340
|
+
outline-color: #000;
|
|
341
341
|
will-change: background-color, outline-color;
|
|
342
342
|
}
|
|
343
343
|
`;
|
|
@@ -354,13 +354,13 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
354
354
|
Displaying {{ args.carouselDataIds.length }} carousel items with {{ args.transitionSpeed }}ms transition speed
|
|
355
355
|
</p>
|
|
356
356
|
<div style="display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap;">
|
|
357
|
-
<div style="padding: 4px 8px; border: 1px solid
|
|
357
|
+
<div style="padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; color: #333;">
|
|
358
358
|
Overflow: {{ args.allowCarouselOverflow ? 'Enabled' : 'Disabled' }}
|
|
359
359
|
</div>
|
|
360
|
-
<div style="padding: 4px 8px; border: 1px solid
|
|
360
|
+
<div style="padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; color: #333;">
|
|
361
361
|
Flip Animation: {{ args.useFlipAnimation ? 'Enabled' : 'Disabled' }}
|
|
362
362
|
</div>
|
|
363
|
-
<div style="padding: 4px 8px; border: 1px solid
|
|
363
|
+
<div style="padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; color: #333;">
|
|
364
364
|
Spring Effect: {{ args.useSpringEffect ? 'Enabled' : 'Disabled' }}
|
|
365
365
|
</div>
|
|
366
366
|
</div>
|
|
@@ -513,7 +513,7 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
513
513
|
.carousel-flip-demo.carousel-flip .item-container {
|
|
514
514
|
max-inline-size: 800px;
|
|
515
515
|
margin-inline: auto;
|
|
516
|
-
outline: 1px solid
|
|
516
|
+
outline: 1px solid var(--slate-08, #666);
|
|
517
517
|
padding-block: 6px;
|
|
518
518
|
padding-inline: 6px;
|
|
519
519
|
}
|
|
@@ -526,12 +526,12 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
.carousel-flip-demo.carousel-flip .item {
|
|
529
|
-
background-color:
|
|
529
|
+
background-color: var(--slate-05, #e5e5e5);
|
|
530
530
|
contain: layout style paint;
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
.carousel-flip-demo.carousel-flip .item:nth-child(odd) {
|
|
534
|
-
background-color:
|
|
534
|
+
background-color: var(--slate-06, #999);
|
|
535
535
|
}
|
|
536
536
|
`;
|
|
537
537
|
document.head.appendChild(style);
|
|
@@ -682,16 +682,16 @@ const AnimationShowcaseTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
|
|
|
682
682
|
Testing different animation combinations and timing
|
|
683
683
|
</p>
|
|
684
684
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 20px;">
|
|
685
|
-
<div style="padding: 8px; border: 1px solid
|
|
685
|
+
<div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.useFlipAnimation ? '#e7f5e7' : 'transparent';">
|
|
686
686
|
Flip: {{ args.useFlipAnimation ? '✓' : '✗' }}
|
|
687
687
|
</div>
|
|
688
|
-
<div style="padding: 8px; border: 1px solid
|
|
688
|
+
<div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.useSpringEffect ? '#e7f5e7' : 'transparent';">
|
|
689
689
|
Spring: {{ args.useSpringEffect ? '✓' : '✗' }}
|
|
690
690
|
</div>
|
|
691
|
-
<div style="padding: 8px; border: 1px solid
|
|
691
|
+
<div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.allowCarouselOverflow ? '#e7f5e7' : 'transparent';">
|
|
692
692
|
Overflow: {{ args.allowCarouselOverflow ? '✓' : '✗' }}
|
|
693
693
|
</div>
|
|
694
|
-
<div style="padding: 8px; border: 1px solid
|
|
694
|
+
<div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333;">
|
|
695
695
|
Speed: {{ args.transitionSpeed }}ms
|
|
696
696
|
</div>
|
|
697
697
|
</div>
|
|
@@ -53,11 +53,11 @@ Same shape, falls back to the inverted-surface slots instead:
|
|
|
53
53
|
|
|
54
54
|
### Tertiary
|
|
55
55
|
|
|
56
|
-
Same shape again
|
|
56
|
+
Same shape again:
|
|
57
57
|
|
|
58
58
|
| Token | Falls back to |
|
|
59
59
|
|---|---|
|
|
60
|
-
| `--input-button-tertiary-surface` | `
|
|
60
|
+
| `--input-button-tertiary-surface` | `var(--slate-01)` |
|
|
61
61
|
| `--input-button-tertiary-text` | `var(--theme-text)` |
|
|
62
62
|
| `--input-button-tertiary-border` | the resolved surface above (flat) |
|
|
63
63
|
| `--input-button-tertiary-ring` / `-ring-hover` | `transparent` |
|
|
@@ -66,11 +66,6 @@ Same shape again; resting surface is transparent by default in dark mode (see no
|
|
|
66
66
|
| `--input-button-tertiary-border-focus` | `var(--theme-border-focus)` |
|
|
67
67
|
| `--input-button-tertiary-ring-focus` | `var(--theme-ring)` |
|
|
68
68
|
|
|
69
|
-
> `--input-button-tertiary-surface`'s dark-mode default is literal `transparent`. `color-mix()`
|
|
70
|
-
> still produces a correct darker hover from that — mixing `transparent` with `black` lowers the
|
|
71
|
-
> alpha channel rather than the lightness, so hover reads as a faint dark tint over whatever's
|
|
72
|
-
> behind the button, not a flat fill swap.
|
|
73
|
-
|
|
74
69
|
`.is-pending` always dims the *primary* surface regardless of variant — a pre-existing quirk, not
|
|
75
70
|
something this token set changes.
|
|
76
71
|
|
|
@@ -196,11 +196,7 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
&.tertiary {
|
|
199
|
-
|
|
200
|
-
darkens correctly here: mixing transparent with black lowers the alpha channel rather
|
|
201
|
-
than the lightness, so it reads as a faint dark tint over whatever's behind the button
|
|
202
|
-
instead of a flat fill. Same formula, no special-casing needed. */
|
|
203
|
-
--_surface: var(--input-button-tertiary-surface, light-dark(var(--slate-01), transparent));
|
|
199
|
+
--_surface: var(--input-button-tertiary-surface, var(--slate-01));
|
|
204
200
|
--_surface-hover: var(
|
|
205
201
|
--input-button-tertiary-surface-hover,
|
|
206
202
|
color-mix(in oklab, var(--_surface) 85%, black)
|
|
@@ -7,16 +7,36 @@ to override just this component; leave it unset and it inherits the shared `--th
|
|
|
7
7
|
`--theme-*` token every other themed component also falls back to. See
|
|
8
8
|
`theming-component-token-pattern.md` for why this two-tier shape exists.
|
|
9
9
|
|
|
10
|
+
This shape mirrors `InputTextCore`'s token set (see its own CONSUMER-STYLING.md) — `InputNumberCore`
|
|
11
|
+
is a native `<input type="number">` sibling to the text-based `InputTextAsNumberWithLabel` variant,
|
|
12
|
+
and both are meant to look and behave the same way. Border colour also doubles as the divider
|
|
13
|
+
colour between the input and an embedded left/right `InputButtonCore` — there's no separate divider
|
|
14
|
+
token, same as `InputTextCore`.
|
|
15
|
+
|
|
10
16
|
| Token | Falls back to | Controls |
|
|
11
17
|
|---|---|---|
|
|
12
|
-
| `--input-number-surface` | `var(--theme-input-surface)` | Wrapper and
|
|
13
|
-
| `--input-number-
|
|
18
|
+
| `--input-number-surface` | `var(--theme-input-surface)` | Wrapper, input, and embedded-button background |
|
|
19
|
+
| `--input-number-surface-hover` | `var(--theme-input-surface-hover)` | Embedded left/right button background on hover |
|
|
20
|
+
| `--input-number-border` | `var(--theme-border)` | Border/divider colour (`normal`/`underlined` variant border, and the divider between the input and an embedded button) |
|
|
21
|
+
| `--input-number-border-hover` | `var(--theme-border-focus)` | Wrapper outline colour on mouse hover (`normal` variant only) |
|
|
22
|
+
| `--input-number-border-focus` | `var(--theme-border-focus)` | Wrapper outline colour on `:focus-visible` (`normal` variant), and the embedded button's own `:focus-visible` outline |
|
|
14
23
|
| `--input-number-text-color` | `var(--theme-input-text-color-normal)` | Input text colour |
|
|
15
|
-
| `--input-number-
|
|
24
|
+
| `--input-number-placeholder-color` | `var(--theme-input-placeholder)` | Placeholder text colour |
|
|
25
|
+
|
|
26
|
+
Geometry (`--input-padding-block`, `--input-padding-inline`, `--input-min-height`,
|
|
27
|
+
`--form-input-border-radius`, `--input-font-size`, etc.) is shared across every `05.forms`
|
|
28
|
+
component and untouched by this — see `theming-form-geometry-tokens.md`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Input variant
|
|
16
33
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`
|
|
34
|
+
`:input-variant="'normal' | 'outlined' | 'underlined'"` (default `"normal"`) — same prop as
|
|
35
|
+
`InputTextCore`. Only `normal` (bordered box, with hover/focus outline) and `underlined`
|
|
36
|
+
(bottom-border only) have CSS today; `outlined` has no styling in `InputTextCore` either (no
|
|
37
|
+
`&.outlined` rule exists there), so passing it renders with no border/outline at all — a
|
|
38
|
+
pre-existing gap in the type, not something specific to `InputNumberCore`. Declared for prop-shape
|
|
39
|
+
consistency with the rest of `05.forms`.
|
|
20
40
|
|
|
21
41
|
---
|
|
22
42
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="input-number-wrapper"
|
|
4
|
+
:data-theme="theme"
|
|
5
|
+
:data-invalid="fieldHasError ? '' : null"
|
|
6
|
+
:class="[inputVariant, { 'has-left-slot': slots.left, 'has-right-slot': slots.right }]"
|
|
7
|
+
>
|
|
8
|
+
<div v-if="slots.left" class="slot left-slot">
|
|
4
9
|
<slot name="left"></slot>
|
|
5
10
|
</div>
|
|
6
11
|
|
|
@@ -15,19 +20,22 @@
|
|
|
15
20
|
:min
|
|
16
21
|
:max
|
|
17
22
|
:step
|
|
18
|
-
:
|
|
23
|
+
:placeholder
|
|
24
|
+
:class="['input-number-core', `input-number--${weight}`, elementClasses]"
|
|
19
25
|
inputmode="numeric"
|
|
20
26
|
pattern="[0-9]+"
|
|
21
27
|
:aria-describedby
|
|
22
28
|
/>
|
|
23
29
|
</div>
|
|
24
|
-
<div v-if="slots.right" class="slot right">
|
|
30
|
+
<div v-if="slots.right" class="slot right-slot">
|
|
25
31
|
<slot name="right"></slot>
|
|
26
32
|
</div>
|
|
27
33
|
</div>
|
|
28
34
|
</template>
|
|
29
35
|
|
|
30
36
|
<script setup lang="ts">
|
|
37
|
+
import type { FormUiTheme, FormWeight, InputUiVariant } from "~/types/forms/types.forms";
|
|
38
|
+
|
|
31
39
|
interface Props {
|
|
32
40
|
id: string;
|
|
33
41
|
name: string;
|
|
@@ -36,7 +44,9 @@ interface Props {
|
|
|
36
44
|
step?: number;
|
|
37
45
|
placeholder?: string;
|
|
38
46
|
required?: boolean;
|
|
39
|
-
theme?:
|
|
47
|
+
theme?: FormUiTheme;
|
|
48
|
+
weight?: FormWeight;
|
|
49
|
+
inputVariant?: InputUiVariant;
|
|
40
50
|
fieldHasError?: boolean;
|
|
41
51
|
styleClassPassthrough?: string | string[];
|
|
42
52
|
ariaDescribedby?: string;
|
|
@@ -47,6 +57,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
47
57
|
placeholder: "",
|
|
48
58
|
required: false,
|
|
49
59
|
theme: "default",
|
|
60
|
+
weight: "normal",
|
|
61
|
+
inputVariant: "normal",
|
|
50
62
|
fieldHasError: false,
|
|
51
63
|
styleClassPassthrough: () => [],
|
|
52
64
|
ariaDescribedby: "",
|
|
@@ -54,7 +66,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
54
66
|
|
|
55
67
|
const slots = useSlots();
|
|
56
68
|
|
|
57
|
-
const modelValue = defineModel<number | readonly number[]>(
|
|
69
|
+
const modelValue = defineModel<number | readonly number[]>({
|
|
70
|
+
required: true,
|
|
71
|
+
});
|
|
58
72
|
|
|
59
73
|
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
60
74
|
const minLength = computed(() => `${props.max.toString().length + 1}em`);
|
|
@@ -62,108 +76,134 @@ const minLength = computed(() => `${props.max.toString().length + 1}em`);
|
|
|
62
76
|
|
|
63
77
|
<style lang="css">
|
|
64
78
|
@layer components {
|
|
65
|
-
.input-number-wrapper {
|
|
66
|
-
|
|
67
|
-
--_min-width: v-bind(minLength);
|
|
79
|
+
.input-number-wrapper {
|
|
80
|
+
--_min-width: v-bind(minLength);
|
|
68
81
|
|
|
69
|
-
|
|
82
|
+
/* Public --input-number-* tokens, inline-fallback to the shared --theme-* tokens (see
|
|
70
83
|
theming-component-token-pattern.md) — overriding one here doesn't touch every other themed
|
|
71
|
-
input/control that also reads --theme-input-surface/--theme-border.
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
input/control that also reads --theme-input-surface/--theme-border. Mirrors InputTextCore's
|
|
85
|
+
token shape so a native number input and the text-based InputTextAsNumberWithLabel variant
|
|
86
|
+
look and behave the same way (see input-number-core.md's "Fixed 2026-09-22" note). */
|
|
87
|
+
--_surface: var(--input-number-surface, var(--theme-input-surface));
|
|
88
|
+
--_border: var(--input-number-border, var(--theme-border));
|
|
89
|
+
--_border-focus: var(--input-number-border-focus, var(--theme-border-focus));
|
|
74
90
|
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
77
93
|
|
|
78
|
-
|
|
94
|
+
width: fit-content;
|
|
79
95
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
background-color: var(--_surface);
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
transition: all var(--theme-form-transition-duration) ease-in-out;
|
|
99
|
+
position: relative;
|
|
100
|
+
z-index: 2;
|
|
101
|
+
|
|
102
|
+
&.normal {
|
|
103
|
+
border: var(--form-element-border-width) solid var(--_border);
|
|
104
|
+
border-radius: var(--form-input-border-radius);
|
|
105
|
+
outline: var(--form-element-outline-width) solid transparent;
|
|
106
|
+
|
|
107
|
+
&:has(input:hover) {
|
|
108
|
+
outline: var(--form-element-outline-width-focus) solid
|
|
109
|
+
var(--input-number-border-hover, var(--theme-border-focus));
|
|
110
|
+
outline-offset: var(--form-element-outline-offset-focus);
|
|
111
|
+
}
|
|
88
112
|
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
&:has(input:focus-visible) {
|
|
114
|
+
outline: var(--form-element-outline-width-focus) solid var(--_border-focus);
|
|
115
|
+
outline-offset: var(--form-element-outline-offset-focus);
|
|
116
|
+
}
|
|
91
117
|
}
|
|
92
|
-
}
|
|
93
118
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
display: flex;
|
|
97
|
-
align-items: center;
|
|
119
|
+
&.underlined {
|
|
120
|
+
border-bottom: var(--form-element-border-bottom-width-underlined) solid var(--_border);
|
|
98
121
|
}
|
|
99
|
-
}
|
|
100
122
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
display: flex;
|
|
104
|
-
align-items: center;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
123
|
+
.slot {
|
|
124
|
+
display: inline-block;
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
border: none;
|
|
112
|
-
outline: none;
|
|
113
|
-
box-shadow: none;
|
|
126
|
+
.icon {
|
|
127
|
+
font-weight: 900;
|
|
128
|
+
}
|
|
114
129
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
.input-button-core {
|
|
131
|
+
background-color: var(--_surface);
|
|
132
|
+
color: var(--input-number-text-color, var(--theme-input-text-color-normal));
|
|
133
|
+
border: none;
|
|
134
|
+
outline: none;
|
|
135
|
+
aspect-ratio: 1;
|
|
136
|
+
border-radius: 0;
|
|
137
|
+
width: var(--input-min-height);
|
|
138
|
+
|
|
139
|
+
&:hover {
|
|
140
|
+
background-color: var(--input-number-surface-hover, var(--theme-input-surface-hover));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
&:is(:focus-visible) {
|
|
144
|
+
outline: var(--form-element-outline-width-focus) solid var(--_border-focus);
|
|
145
|
+
outline-offset: -4px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&.icon-only {
|
|
149
|
+
.btn-icon {
|
|
150
|
+
margin: 0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
129
154
|
}
|
|
130
155
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
font-weight: 400;
|
|
156
|
+
&.has-left-slot {
|
|
157
|
+
.left-slot {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
}
|
|
137
161
|
}
|
|
138
|
-
}
|
|
139
162
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
border: initial;
|
|
145
|
-
border-radius: 0;
|
|
146
|
-
outline: initial;
|
|
147
|
-
box-shadow: unset;
|
|
163
|
+
&.has-right-slot {
|
|
164
|
+
.right-slot {
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
148
167
|
}
|
|
149
168
|
}
|
|
150
169
|
|
|
151
|
-
.left-slot {
|
|
152
|
-
|
|
153
|
-
border-right: 2px solid var(--input-number-divider-color, var(--theme-input-surface-hover));
|
|
170
|
+
&.has-left-slot .left-slot .input-button-core {
|
|
171
|
+
border-right: var(--form-element-border-width) solid var(--_border);
|
|
154
172
|
}
|
|
155
173
|
|
|
156
|
-
.right-slot {
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
&.has-right-slot .right-slot .input-button-core {
|
|
175
|
+
border-left: var(--form-element-border-width) solid var(--_border);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.input-number-core {
|
|
179
|
+
all: unset;
|
|
180
|
+
touch-action: manipulation;
|
|
181
|
+
|
|
182
|
+
color: var(--input-number-text-color, var(--theme-input-text-color-normal));
|
|
183
|
+
font-family: var(--font-family);
|
|
184
|
+
font-size: var(--input-font-size);
|
|
185
|
+
|
|
186
|
+
padding-block: var(--input-padding-block);
|
|
187
|
+
padding-inline: var(--input-padding-inline);
|
|
188
|
+
min-height: var(--input-min-height);
|
|
189
|
+
min-width: var(--_min-width);
|
|
190
|
+
text-align: center;
|
|
191
|
+
|
|
192
|
+
&::placeholder,
|
|
193
|
+
&::-webkit-input-placeholder {
|
|
194
|
+
color: var(--input-number-placeholder-color, var(--theme-input-placeholder));
|
|
195
|
+
font-size: var(--theme-input-placeholder-font-size);
|
|
196
|
+
font-style: italic;
|
|
197
|
+
line-height: 1;
|
|
198
|
+
font-weight: normal;
|
|
199
|
+
}
|
|
159
200
|
}
|
|
160
201
|
}
|
|
161
|
-
}
|
|
162
202
|
|
|
163
|
-
input[type="number"]::-webkit-inner-spin-button,
|
|
164
|
-
input[type="number"]::-webkit-outer-spin-button {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
203
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
204
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
205
|
+
-webkit-appearance: none;
|
|
206
|
+
margin: 0;
|
|
207
|
+
}
|
|
168
208
|
}
|
|
169
209
|
</style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
import StorybookComponent from "../InputNumberCore.vue";
|
|
4
|
+
|
|
5
|
+
interface InputNumberCoreStoryArgs {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
step: number;
|
|
11
|
+
placeholder: string;
|
|
12
|
+
required: boolean;
|
|
13
|
+
fieldHasError: boolean;
|
|
14
|
+
weight: string;
|
|
15
|
+
inputVariant: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
title: "Components/Forms/Input Number/InputNumberCore",
|
|
20
|
+
component: StorybookComponent,
|
|
21
|
+
argTypes: {
|
|
22
|
+
id: { control: "text", table: { category: "Basic" } },
|
|
23
|
+
name: { control: "text", table: { category: "Basic" } },
|
|
24
|
+
min: { control: "number", table: { category: "Basic" } },
|
|
25
|
+
max: { control: "number", table: { category: "Basic" } },
|
|
26
|
+
step: { control: "number", table: { category: "Basic" } },
|
|
27
|
+
placeholder: { control: "text", table: { category: "Basic" } },
|
|
28
|
+
required: { control: "boolean", table: { category: "Basic" } },
|
|
29
|
+
fieldHasError: { control: "boolean", table: { category: "States" } },
|
|
30
|
+
weight: { control: "text", table: { category: "Styling" } },
|
|
31
|
+
inputVariant: {
|
|
32
|
+
control: { type: "select" },
|
|
33
|
+
options: ["normal", "outlined", "underlined"],
|
|
34
|
+
table: { category: "Styling" },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
args: {
|
|
38
|
+
id: "number-default",
|
|
39
|
+
name: "numberDefault",
|
|
40
|
+
min: 0,
|
|
41
|
+
max: 100,
|
|
42
|
+
step: 1,
|
|
43
|
+
placeholder: "",
|
|
44
|
+
required: false,
|
|
45
|
+
fieldHasError: false,
|
|
46
|
+
weight: "normal",
|
|
47
|
+
inputVariant: "normal",
|
|
48
|
+
},
|
|
49
|
+
} as Meta<InputNumberCoreStoryArgs>;
|
|
50
|
+
|
|
51
|
+
const Template: StoryFn<InputNumberCoreStoryArgs> = (args) => ({
|
|
52
|
+
components: { StorybookComponent },
|
|
53
|
+
setup() {
|
|
54
|
+
const modelValue = ref(50);
|
|
55
|
+
return { args, modelValue };
|
|
56
|
+
},
|
|
57
|
+
template: `
|
|
58
|
+
<div style="margin: 36px; max-width: 320px;">
|
|
59
|
+
<StorybookComponent v-bind="args" v-model="modelValue" />
|
|
60
|
+
</div>
|
|
61
|
+
`,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const Default = Template.bind({});
|
|
65
|
+
|
|
66
|
+
export const ErrorState = Template.bind({});
|
|
67
|
+
ErrorState.args = { fieldHasError: true };
|
|
68
|
+
|
|
69
|
+
const WithStepButtonsTemplate: StoryFn<InputNumberCoreStoryArgs> = (args) => ({
|
|
70
|
+
components: { StorybookComponent },
|
|
71
|
+
setup() {
|
|
72
|
+
const modelValue = ref(50);
|
|
73
|
+
return { args, modelValue };
|
|
74
|
+
},
|
|
75
|
+
template: `
|
|
76
|
+
<div style="margin: 36px; max-width: 320px;">
|
|
77
|
+
<StorybookComponent v-bind="args" v-model="modelValue">
|
|
78
|
+
<template #left>
|
|
79
|
+
<button type="button" @click="modelValue = Math.max(args.min, modelValue - args.step)">−</button>
|
|
80
|
+
</template>
|
|
81
|
+
<template #right>
|
|
82
|
+
<button type="button" @click="modelValue = Math.min(args.max, modelValue + args.step)">+</button>
|
|
83
|
+
</template>
|
|
84
|
+
</StorybookComponent>
|
|
85
|
+
</div>
|
|
86
|
+
`,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const WithStepButtons = WithStepButtonsTemplate.bind({});
|
|
90
|
+
|
|
91
|
+
export const Underlined = WithStepButtonsTemplate.bind({});
|
|
92
|
+
Underlined.args = { inputVariant: "underlined" };
|