@boostdev/design-system-components 1.2.0 → 1.2.1

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.
Files changed (126) hide show
  1. package/AGENTS.md +25 -4
  2. package/dist/client.cjs +58 -54
  3. package/dist/client.css +503 -525
  4. package/dist/client.js +58 -54
  5. package/dist/index.cjs +58 -54
  6. package/dist/index.css +503 -525
  7. package/dist/index.js +58 -54
  8. package/dist/web-components/{chunk-6MH5UWUD.js → chunk-2FGATTGT.js} +5 -0
  9. package/dist/web-components/{chunk-5IPHEONG.js → chunk-6I2DBFQ7.js} +1 -0
  10. package/dist/web-components/{chunk-DI46Q2EA.js → chunk-OCODKRVZ.js} +57 -82
  11. package/dist/web-components/index.d.ts +571 -1
  12. package/dist/web-components/index.js +1501 -4
  13. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  14. package/dist/web-components/interaction/bds-drawer.js +1 -1
  15. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  16. package/package.json +1 -1
  17. package/src/components/interaction/Drawer/Drawer.module.css +37 -62
  18. package/src/components/interaction/Drawer/Drawer.tsx +21 -17
  19. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +8 -4
  20. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +13 -6
  21. package/src/stories/Introduction.mdx +2 -1
  22. package/src/web-components/index.ts +12 -0
  23. package/src/web-components/interaction/BdsAccordion.mdx +80 -28
  24. package/src/web-components/interaction/BdsAccordion.stories.tsx +67 -58
  25. package/src/web-components/interaction/BdsCollapsible.mdx +93 -23
  26. package/src/web-components/interaction/BdsCollapsible.stories.tsx +29 -48
  27. package/src/web-components/interaction/BdsDialog.mdx +88 -27
  28. package/src/web-components/interaction/BdsDialog.stories.tsx +129 -47
  29. package/src/web-components/interaction/BdsDrawer.mdx +85 -27
  30. package/src/web-components/interaction/BdsDrawer.stories.tsx +161 -31
  31. package/src/web-components/interaction/BdsDropdownMenu.mdx +108 -0
  32. package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +91 -0
  33. package/src/web-components/interaction/BdsSkipLink.mdx +72 -16
  34. package/src/web-components/interaction/BdsSkipLink.stories.tsx +47 -34
  35. package/src/web-components/interaction/BdsTabs.mdx +97 -27
  36. package/src/web-components/interaction/BdsTabs.stories.tsx +69 -55
  37. package/src/web-components/interaction/BdsTooltip.mdx +84 -18
  38. package/src/web-components/interaction/BdsTooltip.stories.tsx +49 -30
  39. package/src/web-components/interaction/bds-collapsible.ts +1 -0
  40. package/src/web-components/interaction/bds-drawer.ts +59 -82
  41. package/src/web-components/interaction/bds-dropdown-menu-item.ts +124 -0
  42. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +102 -0
  43. package/src/web-components/interaction/bds-dropdown-menu.ts +200 -0
  44. package/src/web-components/interaction/form/BdsCheckbox.mdx +57 -20
  45. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +35 -36
  46. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +70 -0
  47. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +81 -0
  48. package/src/web-components/interaction/form/BdsCombobox.mdx +52 -33
  49. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +55 -53
  50. package/src/web-components/interaction/form/BdsFileInput.mdx +59 -24
  51. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +38 -53
  52. package/src/web-components/interaction/form/BdsFormInput.mdx +98 -0
  53. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +139 -0
  54. package/src/web-components/interaction/form/BdsNumberInput.mdx +51 -22
  55. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +24 -46
  56. package/src/web-components/interaction/form/BdsRadio.mdx +53 -22
  57. package/src/web-components/interaction/form/BdsRadio.stories.tsx +19 -51
  58. package/src/web-components/interaction/form/BdsRadioGroup.mdx +73 -0
  59. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +88 -0
  60. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +76 -23
  61. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +64 -54
  62. package/src/web-components/interaction/form/BdsSelect.mdx +59 -21
  63. package/src/web-components/interaction/form/BdsSelect.stories.tsx +47 -56
  64. package/src/web-components/interaction/form/BdsSlider.mdx +55 -27
  65. package/src/web-components/interaction/form/BdsSlider.stories.tsx +23 -44
  66. package/src/web-components/interaction/form/BdsSwitch.mdx +53 -20
  67. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +22 -37
  68. package/src/web-components/interaction/form/BdsTextarea.mdx +54 -22
  69. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +35 -44
  70. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +55 -0
  71. package/src/web-components/interaction/form/bds-checkbox-group.ts +117 -0
  72. package/src/web-components/interaction/form/bds-form-input.spec.ts +83 -0
  73. package/src/web-components/interaction/form/bds-form-input.ts +268 -0
  74. package/src/web-components/interaction/form/bds-radio-group.spec.ts +62 -0
  75. package/src/web-components/interaction/form/bds-radio-group.ts +142 -0
  76. package/src/web-components/interaction/form/bds-segmented-control.ts +5 -0
  77. package/src/web-components/ui/BdsAvatar.mdx +29 -20
  78. package/src/web-components/ui/BdsAvatar.stories.tsx +14 -28
  79. package/src/web-components/ui/BdsBreadcrumb.mdx +22 -19
  80. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +11 -21
  81. package/src/web-components/ui/BdsButtonGroup.mdx +69 -0
  82. package/src/web-components/ui/BdsButtonGroup.stories.tsx +66 -0
  83. package/src/web-components/ui/BdsCalendar.mdx +93 -0
  84. package/src/web-components/ui/BdsCalendar.stories.tsx +63 -0
  85. package/src/web-components/ui/BdsCard.mdx +30 -25
  86. package/src/web-components/ui/BdsCard.stories.tsx +19 -42
  87. package/src/web-components/ui/BdsCarousel.mdx +83 -0
  88. package/src/web-components/ui/BdsCarousel.stories.tsx +96 -0
  89. package/src/web-components/ui/BdsDescriptionList.mdx +13 -16
  90. package/src/web-components/ui/BdsDescriptionList.stories.tsx +34 -49
  91. package/src/web-components/ui/BdsIconWrapper.mdx +20 -20
  92. package/src/web-components/ui/BdsIconWrapper.stories.tsx +46 -41
  93. package/src/web-components/ui/BdsLink.mdx +21 -17
  94. package/src/web-components/ui/BdsLink.stories.tsx +19 -20
  95. package/src/web-components/ui/BdsLoading.mdx +9 -13
  96. package/src/web-components/ui/BdsLoading.stories.tsx +8 -10
  97. package/src/web-components/ui/BdsNotificationBanner.mdx +32 -37
  98. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +38 -47
  99. package/src/web-components/ui/BdsPagination.mdx +74 -0
  100. package/src/web-components/ui/BdsPagination.stories.tsx +36 -0
  101. package/src/web-components/ui/BdsProgress.mdx +24 -17
  102. package/src/web-components/ui/BdsProgress.stories.tsx +55 -29
  103. package/src/web-components/ui/BdsProgressCircle.mdx +24 -18
  104. package/src/web-components/ui/BdsProgressCircle.stories.tsx +17 -29
  105. package/src/web-components/ui/BdsRating.mdx +22 -22
  106. package/src/web-components/ui/BdsRating.stories.tsx +19 -13
  107. package/src/web-components/ui/BdsSectionHeader.mdx +31 -28
  108. package/src/web-components/ui/BdsSectionHeader.stories.tsx +21 -23
  109. package/src/web-components/ui/BdsSeparator.mdx +22 -8
  110. package/src/web-components/ui/BdsSeparator.stories.tsx +27 -19
  111. package/src/web-components/ui/BdsSkeleton.mdx +20 -26
  112. package/src/web-components/ui/BdsSkeleton.stories.tsx +23 -23
  113. package/src/web-components/ui/BdsTable.mdx +81 -0
  114. package/src/web-components/ui/BdsTable.stories.tsx +83 -0
  115. package/src/web-components/ui/BdsTypography.mdx +20 -29
  116. package/src/web-components/ui/BdsTypography.stories.tsx +19 -18
  117. package/src/web-components/ui/bds-button-group.spec.ts +40 -0
  118. package/src/web-components/ui/bds-button-group.ts +78 -0
  119. package/src/web-components/ui/bds-calendar.spec.ts +91 -0
  120. package/src/web-components/ui/bds-calendar.ts +427 -0
  121. package/src/web-components/ui/bds-carousel.spec.ts +64 -0
  122. package/src/web-components/ui/bds-carousel.ts +296 -0
  123. package/src/web-components/ui/bds-pagination.spec.ts +67 -0
  124. package/src/web-components/ui/bds-pagination.ts +197 -0
  125. package/src/web-components/ui/bds-table.spec.ts +45 -0
  126. package/src/web-components/ui/bds-table.ts +96 -0
@@ -3,42 +3,77 @@ import * as Stories from './BdsFileInput.stories';
3
3
 
4
4
  <Meta of={Stories} />
5
5
 
6
- # File Input
6
+ # &lt;bds-file-input&gt;
7
7
 
8
- `<bds-file-input>` is a form-associated file upload zone with drag-and-drop support. The slot content customises the zone label text.
8
+ Form-associated file upload zone with drag-and-drop support. Renders a styled drop zone that opens the native file picker on click or keyboard activation.
9
9
 
10
- ## Usage
10
+ > **Status: experimental** — API may change before stable release.
11
11
 
12
- ```html
13
- <bds-file-input name="avatar" accept="image/*">Upload your photo</bds-file-input>
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
14
16
  ```
15
17
 
16
- ## Attributes
18
+ ## When to use
17
19
 
18
- | Attribute | Type | Default | Description |
19
- |------------|-----------|---------|------------------------------------------|
20
- | `name` | `string` | `""` | Form field name |
21
- | `accept` | `string` | `""` | File types to accept (e.g. `"image/*"`) |
22
- | `multiple` | `boolean` | `false` | Allow selecting multiple files |
23
- | `disabled` | `boolean` | `false` | Disables the drop zone |
20
+ Use `<bds-file-input>` wherever users need to attach one or more files to a form. It provides drag-and-drop as a progressive enhancement on top of the native file picker.
24
21
 
25
- ## Events
22
+ ## Examples
26
23
 
27
- | Event | Detail | Description |
28
- |--------------|-------------|------------------------------------------|
29
- | `bds-change` | `FileList` | Fired when files are selected or dropped |
24
+ ### Default
25
+ <Canvas of={Stories.Default} />
30
26
 
31
- ## Slots
27
+ ### Multiple files
28
+ <Canvas of={Stories.Multiple} />
32
29
 
33
- | Slot | Description |
34
- |-----------|--------------------------------|
35
- | (default) | Custom label text for the zone |
30
+ ### Accept images only
31
+ <Canvas of={Stories.WithAccept} />
36
32
 
37
- ## Stories
33
+ ### Accept PDFs and Word documents
34
+ <Canvas of={Stories.WithPDFAccept} />
38
35
 
39
- <Canvas of={Stories.Default} />
40
- <Canvas of={Stories.AllVariants} />
36
+ ### Disabled
37
+ <Canvas of={Stories.Disabled} />
41
38
 
42
- ## Controls
39
+ ## Props
43
40
 
44
41
  <ArgTypes of={Stories} />
42
+
43
+ ## Attributes
44
+
45
+ | Attribute | Type | Default | Description |
46
+ |------------|---------|---------|------------------------------------------------------|
47
+ | `name` | string | `""` | Form field name |
48
+ | `accept` | string | `""` | File types to accept (e.g. `"image/*"`, `".pdf"`) |
49
+ | `multiple` | boolean | `false` | Allow selecting multiple files |
50
+ | `disabled` | boolean | `false` | Disables the file input |
51
+
52
+ ## Slots
53
+
54
+ | Slot | Description |
55
+ |-------------|------------------------------------------------|
56
+ | `(default)` | Drop zone call-to-action text. Defaults to "Drag & drop or click to browse" |
57
+
58
+ ## Events
59
+
60
+ | Event | Detail | Description |
61
+ |--------------|--------------|-----------------------------------------|
62
+ | `bds-change` | `FileList` | Fired when files are selected or dropped |
63
+
64
+ ## Usage in plain HTML
65
+
66
+ ```html
67
+ <bds-file-input name="avatar" accept="image/*">Upload photo</bds-file-input>
68
+
69
+ <!-- Multiple files -->
70
+ <bds-file-input name="documents" accept=".pdf,.docx" multiple></bds-file-input>
71
+ ```
72
+
73
+ ## Accessibility
74
+
75
+ - The drop zone has `role="button"` and `tabindex="0"` so it is keyboard-reachable
76
+ - `aria-label="Upload file"` is set on the zone for screen reader identification
77
+ - `Enter` and `Space` open the native file picker from keyboard focus
78
+ - `disabled` sets `tabindex="-1"` and `pointer-events: none`
79
+ - Participates in HTML forms via the Form-Associated Custom Elements API; files are submitted as `FormData` entries
@@ -1,84 +1,69 @@
1
1
  import React from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
- import '../../index';
3
+ import '../../index'; // auto-registers all custom elements
4
4
 
5
5
  function BdsFileInput({
6
+ name,
6
7
  accept,
7
8
  multiple,
8
9
  disabled,
10
+ children,
9
11
  }: {
12
+ name?: string;
10
13
  accept?: string;
11
14
  multiple?: boolean;
12
15
  disabled?: boolean;
16
+ children?: React.ReactNode;
13
17
  }) {
14
- return React.createElement('bds-file-input', {
15
- accept,
16
- multiple: multiple || undefined,
17
- disabled: disabled || undefined,
18
- style: { display: 'block', maxInlineSize: '480px' },
19
- });
18
+ return React.createElement(
19
+ 'bds-file-input',
20
+ {
21
+ name,
22
+ accept,
23
+ multiple: multiple || undefined,
24
+ disabled: disabled || undefined,
25
+ },
26
+ children,
27
+ );
20
28
  }
21
29
 
22
30
  const meta = {
23
31
  title: 'Web Components/Interaction/Form/FileInput',
24
32
  component: BdsFileInput,
25
33
  tags: ['!stable', 'experimental'],
26
- parameters: { layout: 'padded' },
34
+ parameters: { layout: 'centered' },
27
35
  argTypes: {
28
36
  multiple: { control: 'boolean' },
29
37
  disabled: { control: 'boolean' },
30
38
  },
39
+ decorators: [
40
+ (Story: React.ComponentType) => (
41
+ <div style={{ width: '360px' }}>
42
+ <Story />
43
+ </div>
44
+ ),
45
+ ],
31
46
  } satisfies Meta<typeof BdsFileInput>;
32
47
 
33
48
  export default meta;
34
49
  type Story = StoryObj<typeof meta>;
35
50
 
36
- export const Default: Story = { args: {} };
37
- export const ImagesOnly: Story = { args: { accept: 'image/*' } };
38
- export const Multiple: Story = { args: { multiple: true } };
39
- export const Disabled: Story = { args: { disabled: true } };
51
+ export const Default: Story = {
52
+ args: { name: 'file' },
53
+ };
40
54
 
41
- export const AllVariants: Story = {
42
- render: () => (
43
- <div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxInlineSize: '480px' }}>
44
- <div>
45
- <p style={{ margin: '0 0 8px', fontSize: '0.75rem', color: '#888' }}>Any file type</p>
46
- {React.createElement('bds-file-input', { style: { display: 'block' } })}
47
- </div>
48
- <div>
49
- <p style={{ margin: '0 0 8px', fontSize: '0.75rem', color: '#888' }}>Images only</p>
50
- {React.createElement('bds-file-input', { accept: 'image/*', style: { display: 'block' } })}
51
- </div>
52
- <div>
53
- <p style={{ margin: '0 0 8px', fontSize: '0.75rem', color: '#888' }}>Multiple files</p>
54
- {React.createElement('bds-file-input', { multiple: true, style: { display: 'block' } })}
55
- </div>
56
- <div>
57
- <p style={{ margin: '0 0 8px', fontSize: '0.75rem', color: '#888' }}>Disabled</p>
58
- {React.createElement('bds-file-input', { disabled: true, style: { display: 'block' } })}
59
- </div>
60
- </div>
61
- ),
55
+ export const Multiple: Story = {
56
+ args: { name: 'files', multiple: true },
62
57
  };
63
58
 
64
- export const Interactive: Story = {
65
- render: () => {
66
- const [files, setFiles] = React.useState<string[]>([]);
67
- return (
68
- <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', maxInlineSize: '480px' }}>
69
- {React.createElement('bds-file-input', {
70
- multiple: true,
71
- style: { display: 'block' },
72
- 'onbds-change': (e: CustomEvent<FileList>) => {
73
- setFiles(Array.from(e.detail).map(f => f.name));
74
- },
75
- })}
76
- {files.length > 0 && (
77
- <ul style={{ margin: 0, padding: '0 0 0 16px', fontSize: '0.875rem' }}>
78
- {files.map(name => <li key={name}>{name}</li>)}
79
- </ul>
80
- )}
81
- </div>
82
- );
83
- },
59
+ export const WithAccept: Story = {
60
+ args: { name: 'avatar', accept: 'image/*' },
61
+ };
62
+
63
+ export const WithPDFAccept: Story = {
64
+ args: { name: 'document', accept: '.pdf,.docx' },
65
+ };
66
+
67
+ export const Disabled: Story = {
68
+ args: { name: 'file', disabled: true },
84
69
  };
@@ -0,0 +1,98 @@
1
+ import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
2
+ import * as Stories from './BdsFormInput.stories';
3
+
4
+ <Meta of={Stories} />
5
+
6
+ # &lt;bds-form-input&gt;
7
+
8
+ Form-associated text input with label, hint, error, and prefix/suffix slot support. Participates in HTML forms via the Form-Associated Custom Elements API.
9
+
10
+ > **Status: experimental** — API may change before stable release.
11
+
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
16
+ ```
17
+
18
+ ## When to use
19
+
20
+ Use `<bds-form-input>` for single-line text fields in forms. It handles label association, validation states, and prefix/suffix adornments in a single element.
21
+
22
+ ## Examples
23
+
24
+ ### Default
25
+ <Canvas of={Stories.Default} />
26
+
27
+ ### With label
28
+ <Canvas of={Stories.WithLabel} />
29
+
30
+ ### With hint
31
+ <Canvas of={Stories.WithHint} />
32
+
33
+ ### With error
34
+ <Canvas of={Stories.WithError} />
35
+
36
+ ### With prefix (currency symbol)
37
+ <Canvas of={Stories.WithPrefix} />
38
+
39
+ ### With suffix (unit)
40
+ <Canvas of={Stories.WithSuffix} />
41
+
42
+ ### Email type
43
+ <Canvas of={Stories.Email} />
44
+
45
+ ### Disabled
46
+ <Canvas of={Stories.Disabled} />
47
+
48
+ ## Props
49
+
50
+ <ArgTypes of={Stories} />
51
+
52
+ ## Attributes
53
+
54
+ | Attribute | Type | Default | Description |
55
+ |---------------|---------|----------|----------------------------------------------------------------|
56
+ | `name` | string | `""` | Form field name |
57
+ | `type` | string | `"text"` | Input type: `text`, `email`, `password`, `search`, `url`, `tel` |
58
+ | `value` | string | `""` | Current input value |
59
+ | `placeholder` | string | `""` | Placeholder text |
60
+ | `label` | string | `""` | Visible label text |
61
+ | `hint` | string | — | Helper text shown below the input |
62
+ | `error` | string | — | Error message; when set the input enters error state |
63
+ | `disabled` | boolean | `false` | Disables the input |
64
+ | `required` | boolean | `false` | Marks the field as required |
65
+
66
+ ## Slots
67
+
68
+ | Slot | Description |
69
+ |----------|-------------------------------------------|
70
+ | `prefix` | Icon or text rendered before the input |
71
+ | `suffix` | Icon or text rendered after the input |
72
+
73
+ ## Events
74
+
75
+ | Event | Detail | Description |
76
+ |--------------|-------------------------|-------------------------------------|
77
+ | `bds-input` | `{ value: string }` | Fired on every keystroke |
78
+ | `bds-change` | `{ value: string }` | Fired when the value is committed |
79
+
80
+ ## Usage in plain HTML
81
+
82
+ ```html
83
+ <bds-form-input name="email" type="email" label="Email address" required></bds-form-input>
84
+
85
+ <!-- With prefix -->
86
+ <bds-form-input name="price" label="Price">
87
+ <span slot="prefix">€</span>
88
+ </bds-form-input>
89
+ ```
90
+
91
+ ## Accessibility
92
+
93
+ - `<label>` is associated with the inner `<input>` via matching `id`/`for` attributes
94
+ - `required` adds `aria-required="true"` and a visual asterisk
95
+ - `error` sets `aria-invalid="true"` and links the error message via `aria-describedby`
96
+ - `hint` is linked via `aria-describedby` for screen reader announcement
97
+ - Error messages use `role="alert"` so they are announced immediately
98
+ - The focus ring is applied to the wrapper element, not the inner input
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import '../../index'; // auto-registers all custom elements
4
+
5
+ function BdsFormInput({
6
+ name,
7
+ type,
8
+ value,
9
+ placeholder,
10
+ label,
11
+ hint,
12
+ error,
13
+ disabled,
14
+ required,
15
+ prefix,
16
+ suffix,
17
+ }: {
18
+ name?: string;
19
+ type?: string;
20
+ value?: string;
21
+ placeholder?: string;
22
+ label?: string;
23
+ hint?: string;
24
+ error?: string;
25
+ disabled?: boolean;
26
+ required?: boolean;
27
+ prefix?: React.ReactNode;
28
+ suffix?: React.ReactNode;
29
+ }) {
30
+ const prefixSlot = prefix
31
+ ? React.createElement('span', { slot: 'prefix' }, prefix)
32
+ : undefined;
33
+ const suffixSlot = suffix
34
+ ? React.createElement('span', { slot: 'suffix' }, suffix)
35
+ : undefined;
36
+
37
+ return React.createElement(
38
+ 'bds-form-input',
39
+ {
40
+ name,
41
+ type,
42
+ value,
43
+ placeholder,
44
+ label,
45
+ hint,
46
+ error,
47
+ disabled: disabled || undefined,
48
+ required: required || undefined,
49
+ },
50
+ prefixSlot,
51
+ suffixSlot,
52
+ );
53
+ }
54
+
55
+ const meta = {
56
+ title: 'Web Components/Interaction/Form/FormInput',
57
+ component: BdsFormInput,
58
+ tags: ['!stable', 'experimental'],
59
+ parameters: { layout: 'centered' },
60
+ argTypes: {
61
+ type: {
62
+ control: 'select',
63
+ options: ['text', 'email', 'password', 'search', 'url', 'tel'],
64
+ },
65
+ disabled: { control: 'boolean' },
66
+ required: { control: 'boolean' },
67
+ },
68
+ decorators: [
69
+ (Story: React.ComponentType) => (
70
+ <div style={{ width: '320px' }}>
71
+ <Story />
72
+ </div>
73
+ ),
74
+ ],
75
+ } satisfies Meta<typeof BdsFormInput>;
76
+
77
+ export default meta;
78
+ type Story = StoryObj<typeof meta>;
79
+
80
+ export const Default: Story = {
81
+ args: { name: 'text', placeholder: 'Enter text…' },
82
+ };
83
+
84
+ export const WithLabel: Story = {
85
+ args: { name: 'username', label: 'Username', placeholder: 'e.g. johndoe' },
86
+ };
87
+
88
+ export const WithHint: Story = {
89
+ args: {
90
+ name: 'username',
91
+ label: 'Username',
92
+ placeholder: 'e.g. johndoe',
93
+ hint: 'Must be 3–20 characters.',
94
+ },
95
+ };
96
+
97
+ export const WithError: Story = {
98
+ args: {
99
+ name: 'username',
100
+ label: 'Username',
101
+ placeholder: 'e.g. johndoe',
102
+ error: 'Username is already taken.',
103
+ },
104
+ };
105
+
106
+ export const WithPrefix: Story = {
107
+ render: () => (
108
+ <div style={{ width: '320px' }}>
109
+ <BdsFormInput name="price" label="Price" placeholder="0.00" prefix="€" />
110
+ </div>
111
+ ),
112
+ };
113
+
114
+ export const WithSuffix: Story = {
115
+ render: () => (
116
+ <div style={{ width: '320px' }}>
117
+ <BdsFormInput name="weight" label="Weight" placeholder="0" suffix="kg" />
118
+ </div>
119
+ ),
120
+ };
121
+
122
+ export const Email: Story = {
123
+ args: {
124
+ name: 'email',
125
+ type: 'email',
126
+ label: 'Email address',
127
+ placeholder: 'you@example.com',
128
+ required: true,
129
+ },
130
+ };
131
+
132
+ export const Disabled: Story = {
133
+ args: {
134
+ name: 'username',
135
+ label: 'Username',
136
+ value: 'johndoe',
137
+ disabled: true,
138
+ },
139
+ };
@@ -3,39 +3,68 @@ import * as Stories from './BdsNumberInput.stories';
3
3
 
4
4
  <Meta of={Stories} />
5
5
 
6
- # Number Input
6
+ # &lt;bds-number-input&gt;
7
7
 
8
- `<bds-number-input>` is a form-associated number input with decrement/increment stepper buttons. The native browser spin buttons are hidden in favour of the custom steppers.
8
+ Form-associated number input with increment and decrement stepper buttons. Hides the browser's native spinners and provides styled stepper controls instead.
9
9
 
10
- ## Usage
10
+ > **Status: experimental** — API may change before stable release.
11
11
 
12
- ```html
13
- <bds-number-input name="qty" value="1" min="0" max="99" step="1"></bds-number-input>
12
+ ## Installation
13
+
14
+ ```js
15
+ import '@boostdev/components/web-components';
14
16
  ```
15
17
 
18
+ ## When to use
19
+
20
+ Use `<bds-number-input>` for quantity selectors, counters, or any numeric field where step-by-step incrementing is a common interaction pattern.
21
+
22
+ ## Examples
23
+
24
+ ### Default
25
+ <Canvas of={Stories.Default} />
26
+
27
+ ### With min and max
28
+ <Canvas of={Stories.WithMinMax} />
29
+
30
+ ### With custom step (5)
31
+ <Canvas of={Stories.WithStep} />
32
+
33
+ ### Disabled
34
+ <Canvas of={Stories.Disabled} />
35
+
36
+ ## Props
37
+
38
+ <ArgTypes of={Stories} />
39
+
16
40
  ## Attributes
17
41
 
18
- | Attribute | Type | Default | Description |
19
- |------------|-----------|-------------|------------------------------------|
20
- | `name` | `string` | `""` | Form field name |
21
- | `value` | `number` | `0` | Current numeric value |
22
- | `min` | `number` | `undefined` | Minimum allowed value |
23
- | `max` | `number` | `undefined` | Maximum allowed value |
24
- | `step` | `number` | `1` | Increment/decrement step |
25
- | `disabled` | `boolean` | `false` | Disables all interaction |
42
+ | Attribute | Type | Default | Description |
43
+ |------------|---------|-------------|------------------------------------------|
44
+ | `name` | string | `""` | Form field name |
45
+ | `value` | number | `0` | Current numeric value |
46
+ | `min` | number | | Minimum allowed value |
47
+ | `max` | number | | Maximum allowed value |
48
+ | `step` | number | `1` | Increment/decrement step |
49
+ | `disabled` | boolean | `false` | Disables the input and steppers |
26
50
 
27
51
  ## Events
28
52
 
29
- | Event | Description |
30
- |----------|--------------------------------------------|
31
- | `input` | Fired on stepper click and native input |
32
- | `change` | Native change event |
53
+ | Event | Detail | Description |
54
+ |----------|--------|-------------------------------------------------------|
55
+ | `input` | — | Fired on stepper click and native input |
56
+ | `change` | — | Native change event from the inner `<input type="number">` |
33
57
 
34
- ## Stories
58
+ ## Usage in plain HTML
35
59
 
36
- <Canvas of={Stories.Default} />
37
- <Canvas of={Stories.AllVariants} />
60
+ ```html
61
+ <bds-number-input name="qty" value="1" min="0" max="99" step="1"></bds-number-input>
62
+ ```
38
63
 
39
- ## Controls
64
+ ## Accessibility
40
65
 
41
- <ArgTypes of={Stories} />
66
+ - Decrement button has `aria-label="Decrease"` and increment has `aria-label="Increase"`
67
+ - Stepper buttons are disabled when the value is at `min` or `max`
68
+ - The stepper buttons use `tabindex="-1"` so keyboard users navigate directly to the numeric input
69
+ - `disabled` disables both the input and the stepper buttons
70
+ - Participates in HTML forms via the Form-Associated Custom Elements API
@@ -1,14 +1,16 @@
1
1
  import React from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
- import '../../index';
3
+ import '../../index'; // auto-registers all custom elements
4
4
 
5
5
  function BdsNumberInput({
6
+ name,
6
7
  value,
7
8
  min,
8
9
  max,
9
10
  step,
10
11
  disabled,
11
12
  }: {
13
+ name?: string;
12
14
  value?: number;
13
15
  min?: number;
14
16
  max?: number;
@@ -16,12 +18,12 @@ function BdsNumberInput({
16
18
  disabled?: boolean;
17
19
  }) {
18
20
  return React.createElement('bds-number-input', {
21
+ name,
19
22
  value,
20
23
  min,
21
24
  max,
22
25
  step,
23
26
  disabled: disabled || undefined,
24
- style: { display: 'block', maxInlineSize: '160px' },
25
27
  });
26
28
  }
27
29
 
@@ -29,62 +31,38 @@ const meta = {
29
31
  title: 'Web Components/Interaction/Form/NumberInput',
30
32
  component: BdsNumberInput,
31
33
  tags: ['!stable', 'experimental'],
32
- parameters: { layout: 'padded' },
34
+ parameters: { layout: 'centered' },
33
35
  argTypes: {
36
+ disabled: { control: 'boolean' },
34
37
  value: { control: 'number' },
35
38
  min: { control: 'number' },
36
39
  max: { control: 'number' },
37
40
  step: { control: 'number' },
38
- disabled: { control: 'boolean' },
39
41
  },
42
+ decorators: [
43
+ (Story: React.ComponentType) => (
44
+ <div style={{ width: '200px' }}>
45
+ <Story />
46
+ </div>
47
+ ),
48
+ ],
40
49
  } satisfies Meta<typeof BdsNumberInput>;
41
50
 
42
51
  export default meta;
43
52
  type Story = StoryObj<typeof meta>;
44
53
 
45
- export const Default: Story = { args: { value: 0 } };
46
- export const WithBounds: Story = { args: { value: 5, min: 0, max: 10 } };
47
- export const WithStep: Story = { args: { value: 0, min: 0, max: 100, step: 5 } };
48
- export const Disabled: Story = { args: { value: 42, disabled: true } };
54
+ export const Default: Story = {
55
+ args: { name: 'quantity', value: 0 },
56
+ };
49
57
 
50
- export const AllVariants: Story = {
51
- render: () => (
52
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
53
- <div>
54
- <p style={{ margin: '0 0 4px', fontSize: '0.75rem', color: '#888' }}>Default</p>
55
- {React.createElement('bds-number-input', { value: 0, style: { display: 'block', maxInlineSize: '160px' } })}
56
- </div>
57
- <div>
58
- <p style={{ margin: '0 0 4px', fontSize: '0.75rem', color: '#888' }}>Bounded (0–10)</p>
59
- {React.createElement('bds-number-input', { value: 5, min: 0, max: 10, style: { display: 'block', maxInlineSize: '160px' } })}
60
- </div>
61
- <div>
62
- <p style={{ margin: '0 0 4px', fontSize: '0.75rem', color: '#888' }}>Step 5</p>
63
- {React.createElement('bds-number-input', { value: 50, min: 0, max: 100, step: 5, style: { display: 'block', maxInlineSize: '160px' } })}
64
- </div>
65
- <div>
66
- <p style={{ margin: '0 0 4px', fontSize: '0.75rem', color: '#888' }}>Disabled</p>
67
- {React.createElement('bds-number-input', { value: 7, disabled: true, style: { display: 'block', maxInlineSize: '160px' } })}
68
- </div>
69
- </div>
70
- ),
58
+ export const WithMinMax: Story = {
59
+ args: { name: 'quantity', value: 5, min: 0, max: 10 },
71
60
  };
72
61
 
73
- export const Interactive: Story = {
74
- render: () => {
75
- const [qty, setQty] = React.useState(1);
76
- return (
77
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
78
- <p style={{ margin: '0 0 4px', fontSize: '0.875rem' }}>Quantity: {qty}</p>
79
- {React.createElement('bds-number-input', {
80
- value: qty,
81
- min: 1,
82
- max: 99,
83
- step: 1,
84
- style: { display: 'block', maxInlineSize: '160px' },
85
- onInput: (e: Event) => setQty(parseInt((e.target as HTMLInputElement).value, 10) || 1),
86
- })}
87
- </div>
88
- );
89
- },
62
+ export const WithStep: Story = {
63
+ args: { name: 'quantity', value: 0, step: 5, min: 0, max: 100 },
64
+ };
65
+
66
+ export const Disabled: Story = {
67
+ args: { name: 'quantity', value: 3, disabled: true },
90
68
  };