@citizenplane/pimp 16.0.3 → 16.1.0

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 (61) hide show
  1. package/dist/pimp.es.js +313 -285
  2. package/dist/pimp.umd.js +21 -21
  3. package/dist/style.css +1 -1
  4. package/package.json +2 -1
  5. package/src/components/CpHeading.vue +4 -5
  6. package/src/components/CpText.vue +141 -0
  7. package/src/components/index.ts +2 -0
  8. package/src/stories/BaseInputLabel.stories.ts +36 -9
  9. package/src/stories/Colors.mdx +9 -0
  10. package/src/stories/Colors.stories.ts +177 -0
  11. package/src/stories/CpAccordion.stories.ts +187 -158
  12. package/src/stories/CpAccordionGroup.stories.ts +50 -94
  13. package/src/stories/CpAirlineLogo.stories.ts +49 -28
  14. package/src/stories/CpAlert.stories.ts +195 -158
  15. package/src/stories/CpBadge.stories.ts +259 -193
  16. package/src/stories/CpButton.stories.ts +257 -426
  17. package/src/stories/CpCheckbox.stories.ts +101 -29
  18. package/src/stories/CpContextualMenu.stories.ts +9 -8
  19. package/src/stories/CpDate.stories.ts +52 -25
  20. package/src/stories/CpDatepicker.stories.ts +57 -88
  21. package/src/stories/CpDialog.stories.ts +22 -1
  22. package/src/stories/CpHeading.stories.ts +59 -20
  23. package/src/stories/CpIcon.stories.ts +98 -31
  24. package/src/stories/CpInput.stories.ts +142 -67
  25. package/src/stories/CpItemActions.stories.ts +22 -27
  26. package/src/stories/CpLoader.stories.ts +54 -6
  27. package/src/stories/CpMenuItem.stories.ts +52 -26
  28. package/src/stories/CpMultiselect.stories.ts +52 -71
  29. package/src/stories/CpPartnerBadge.stories.ts +53 -74
  30. package/src/stories/CpRadio.stories.ts +44 -48
  31. package/src/stories/CpRadioGroup.stories.ts +46 -39
  32. package/src/stories/CpSelect.stories.ts +98 -39
  33. package/src/stories/CpSelectMenu.stories.ts +49 -57
  34. package/src/stories/CpSelectableButton.stories.ts +170 -81
  35. package/src/stories/CpSwitch.stories.ts +135 -133
  36. package/src/stories/CpTable.stories.ts +54 -1
  37. package/src/stories/CpTableEmptyState.stories.ts +11 -7
  38. package/src/stories/CpTabs.stories.ts +22 -4
  39. package/src/stories/CpTelInput.stories.ts +25 -23
  40. package/src/stories/CpText.stories.ts +131 -0
  41. package/src/stories/CpTextarea.stories.ts +59 -23
  42. package/src/stories/CpToast.stories.ts +53 -103
  43. package/src/stories/CpTooltip.stories.ts +82 -77
  44. package/src/stories/CpTransitionCounter.stories.ts +4 -0
  45. package/src/stories/CpTransitionExpand.stories.ts +11 -6
  46. package/src/stories/CpTransitionListItems.stories.ts +5 -0
  47. package/src/stories/CpTransitionSize.stories.ts +8 -0
  48. package/src/stories/CpTransitionSlide.stories.ts +4 -0
  49. package/src/stories/CpTransitionTabContent.stories.ts +4 -0
  50. package/src/stories/Dimensions.mdx +9 -0
  51. package/src/stories/Dimensions.stories.ts +119 -0
  52. package/src/stories/Easings.mdx +9 -0
  53. package/src/stories/Easings.stories.ts +101 -0
  54. package/src/stories/FocusRings.mdx +9 -0
  55. package/src/stories/FocusRings.stories.ts +74 -0
  56. package/src/stories/Shadows.mdx +9 -0
  57. package/src/stories/Shadows.stories.ts +100 -0
  58. package/src/stories/Typography.mdx +9 -0
  59. package/src/stories/Typography.stories.ts +181 -0
  60. package/src/stories/documentationStyles.ts +2 -10
  61. package/src/stories/tokenUtils.ts +259 -0
@@ -1,36 +1,31 @@
1
- import type { Meta, StoryObj } from '@storybook/vue3'
2
-
3
- import { ButtonAppearances } from '@/constants/Button'
1
+ import type { Args, Meta, StoryObj } from '@storybook/vue3'
4
2
 
5
3
  import CpButton from '@/components/CpButton.vue'
6
4
  import CpIcon from '@/components/CpIcon.vue'
7
5
 
8
- import {
9
- docCellStyle,
10
- docLabelStyle,
11
- docPageStyle,
12
- docRowWrapStyle,
13
- docSectionStyle,
14
- docTitleStyle,
15
- } from '@/stories/documentationStyles'
6
+ import { docCellStyle, docLabelStyle, docRowWrapStyle } from '@/stories/documentationStyles'
7
+
8
+ const buttonAppearances = ['primary', 'secondary', 'tertiary'] as const
9
+ const buttonColors = ['neutral', 'accent', 'success', 'warning', 'error'] as const
10
+ const buttonSizes = ['2xs', 'xs', 'sm', 'md', 'lg'] as const
16
11
 
17
12
  const meta = {
18
- title: 'Form/CpButton',
13
+ title: 'Atoms/CpButton',
19
14
  component: CpButton,
20
15
  argTypes: {
21
16
  appearance: {
22
17
  control: 'select',
23
- options: ['primary', 'secondary', 'tertiary'],
18
+ options: buttonAppearances,
24
19
  description: 'The visual style of the button',
25
20
  },
26
21
  size: {
27
22
  control: 'select',
28
- options: ['2xs', 'xs', 'sm', 'md', 'lg'],
23
+ options: buttonSizes,
29
24
  description: 'The size of the button',
30
25
  },
31
26
  color: {
32
27
  control: 'select',
33
- options: ['Neutral', 'Accent', 'Success', 'Warning', 'Error'],
28
+ options: buttonColors,
34
29
  description: 'The color variant of the button',
35
30
  },
36
31
  disabled: {
@@ -55,15 +50,20 @@ const meta = {
55
50
  control: 'boolean',
56
51
  description: 'Whether the button has square corners',
57
52
  },
53
+ enableHaptics: {
54
+ control: 'boolean',
55
+ description: 'Trigger haptic feedback on click (supported devices only)',
56
+ },
58
57
  },
59
58
  } satisfies Meta<typeof CpButton>
60
59
 
61
60
  export default meta
61
+
62
62
  type Story = StoryObj<typeof meta>
63
63
 
64
64
  const defaultTemplate = '<CpButton v-bind="args">Button</CpButton>'
65
65
 
66
- const defaultRender = (args: (typeof CpButton)['props']) => ({
66
+ const defaultRender = (args: Args) => ({
67
67
  components: { CpButton },
68
68
  setup() {
69
69
  return { args }
@@ -71,307 +71,206 @@ const defaultRender = (args: (typeof CpButton)['props']) => ({
71
71
  template: defaultTemplate,
72
72
  })
73
73
 
74
- export const Documentation: Story = {
75
- args: {},
76
- render: () => ({
77
- components: { CpButton, CpIcon },
78
- setup: () => ({}),
79
- template: `
80
- <div>
81
- <div style="${docPageStyle}">
82
- <h1 style="margin: 0 0 32px 0; font-size: 28px; font-weight: 700; color: #111;">CpButton</h1>
83
-
84
- <section style="${docSectionStyle}">
85
- <h2 style="${docTitleStyle}">Size</h2>
86
- <div style="${docRowWrapStyle}">
87
- <div style="${docCellStyle}">
88
- <span style="${docLabelStyle}">2xs</span>
89
- <CpButton color="accent" size="2xs">Default button</CpButton>
90
- </div>
91
- <div style="${docCellStyle}">
92
- <span style="${docLabelStyle}">xs</span>
93
- <CpButton color="accent" size="xs">Default button</CpButton>
94
- </div>
95
- <div style="${docCellStyle}">
96
- <span style="${docLabelStyle}">sm</span>
97
- <CpButton color="accent" size="sm">Default button</CpButton>
98
- </div>
99
- <div style="${docCellStyle}">
100
- <span style="${docLabelStyle}">md</span>
101
- <CpButton color="accent" size="md">Default button</CpButton>
102
- </div>
103
- <div style="${docCellStyle}">
104
- <span style="${docLabelStyle}">lg</span>
105
- <CpButton color="accent" size="lg">Default button</CpButton>
106
- </div>
107
- </div>
108
- </section>
109
-
110
- <section style="${docSectionStyle}">
111
- <h2 style="${docTitleStyle}">Color</h2>
112
- <div style="${docRowWrapStyle}">
113
- <div style="${docCellStyle}">
114
- <span style="${docLabelStyle}">neutral</span>
115
- <CpButton color="neutral">Default button</CpButton>
116
- </div>
117
- <div style="${docCellStyle}">
118
- <span style="${docLabelStyle}">accent</span>
119
- <CpButton color="accent">Default button</CpButton>
120
- </div>
121
- <div style="${docCellStyle}">
122
- <span style="${docLabelStyle}">error</span>
123
- <CpButton color="error">Default button</CpButton>
124
- </div>
125
- <div style="${docCellStyle}">
126
- <span style="${docLabelStyle}">warning</span>
127
- <CpButton color="warning">Default button</CpButton>
128
- </div>
129
- <div style="${docCellStyle}">
130
- <span style="${docLabelStyle}">success</span>
131
- <CpButton color="success">Default button</CpButton>
132
- </div>
133
- </div>
134
- </section>
74
+ /**
75
+ * Default button: primary appearance, neutral color, medium size. Use the
76
+ * controls to experiment with each prop in isolation.
77
+ */
78
+ export const Default: Story = {
79
+ args: {
80
+ appearance: 'primary',
81
+ color: 'neutral',
82
+ size: 'md',
83
+ disabled: false,
84
+ isLoading: false,
85
+ isSquare: false,
86
+ tag: 'button',
87
+ type: 'button',
88
+ },
89
+ render: defaultRender,
90
+ }
135
91
 
136
- <section style="${docSectionStyle}">
137
- <h2 style="${docTitleStyle}">State</h2>
138
- <div style="${docRowWrapStyle}">
139
- <div style="${docCellStyle}">
140
- <span style="${docLabelStyle}">default</span>
141
- <CpButton color="accent">Default button</CpButton>
142
- </div>
143
- <div style="${docCellStyle}">
144
- <span style="${docLabelStyle}">disabled</span>
145
- <CpButton color="accent" :disabled="true">Default button</CpButton>
146
- </div>
147
- </div>
148
- </section>
92
+ /* -------------------------------------------------------------------------- */
93
+ /* Appearances */
94
+ /* -------------------------------------------------------------------------- */
149
95
 
150
- <section style="${docSectionStyle}">
151
- <h2 style="${docTitleStyle}">Squared</h2>
152
- <div style="${docRowWrapStyle}">
153
- <div style="${docCellStyle}">
154
- <span style="${docLabelStyle}">false</span>
155
- <CpButton color="accent" :is-square="false">Default button</CpButton>
156
- </div>
157
- <div style="${docCellStyle}">
158
- <span style="${docLabelStyle}">true</span>
159
- <CpButton color="accent" :is-square="true">Default button</CpButton>
160
- </div>
161
- </div>
162
- </section>
96
+ /**
97
+ * Every appearance rendered side by side: `primary`, `secondary`, `tertiary`.
98
+ */
99
+ export const Appearances: Story = {
100
+ parameters: { controls: { disable: true } },
101
+ render: () => ({
102
+ components: { CpButton },
103
+ setup() {
104
+ return { buttonAppearances, docCellStyle, docLabelStyle, docRowWrapStyle }
105
+ },
106
+ template: `
107
+ <div :style="docRowWrapStyle">
108
+ <div v-for="appearance in buttonAppearances" :key="appearance" :style="docCellStyle">
109
+ <span :style="docLabelStyle">{{ appearance }}</span>
110
+ <CpButton :appearance="appearance" color="accent">Button</CpButton>
111
+ </div>
112
+ </div>
113
+ `,
114
+ }),
115
+ }
163
116
 
164
- <section style="${docSectionStyle}">
165
- <h2 style="${docTitleStyle}">Style</h2>
166
- <div style="${docRowWrapStyle}">
167
- <div style="${docCellStyle}">
168
- <span style="${docLabelStyle}">primary</span>
169
- <CpButton color="accent">Default button</CpButton>
170
- </div>
171
- <div style="${docCellStyle}">
172
- <span style="${docLabelStyle}">secondary</span>
173
- <CpButton appearance="secondary" color="accent">Default button</CpButton>
174
- </div>
175
- <div style="${docCellStyle}">
176
- <span style="${docLabelStyle}">tertiary</span>
177
- <CpButton appearance="tertiary" color="accent">Default button</CpButton>
178
- </div>
179
- </div>
180
- </section>
117
+ /* -------------------------------------------------------------------------- */
118
+ /* Colors */
119
+ /* -------------------------------------------------------------------------- */
181
120
 
182
- <section style="${docSectionStyle}">
183
- <h2 style="${docTitleStyle}">Icon</h2>
184
- <div style="${docRowWrapStyle}">
185
- <div style="${docCellStyle}">
186
- <span style="${docLabelStyle}">—</span>
187
- <CpButton color="accent">Default button</CpButton>
188
- </div>
189
- <div style="${docCellStyle}">
190
- <span style="${docLabelStyle}">leading</span>
191
- <CpButton color="accent">
192
- <template #leading-icon><CpIcon type="plus" /></template>
193
- Default button
194
- </CpButton>
195
- </div>
196
- <div style="${docCellStyle}">
197
- <span style="${docLabelStyle}">leading + trailing</span>
198
- <CpButton color="accent">
199
- <template #leading-icon><CpIcon type="plus" /></template>
200
- Default button
201
- <template #trailing-icon><CpIcon type="plus" /></template>
202
- </CpButton>
203
- </div>
204
- <div style="${docCellStyle}">
205
- <span style="${docLabelStyle}">trailing</span>
206
- <CpButton color="accent">
207
- Default button
208
- <template #trailing-icon><CpIcon type="plus" /></template>
209
- </CpButton>
210
- </div>
211
- </div>
212
- </section>
121
+ /**
122
+ * Every semantic color available for the button.
123
+ */
124
+ export const Colors: Story = {
125
+ parameters: { controls: { disable: true } },
126
+ render: () => ({
127
+ components: { CpButton },
128
+ setup() {
129
+ return { buttonColors, docCellStyle, docLabelStyle, docRowWrapStyle }
130
+ },
131
+ template: `
132
+ <div :style="docRowWrapStyle">
133
+ <div v-for="color in buttonColors" :key="color" :style="docCellStyle">
134
+ <span :style="docLabelStyle">{{ color }}</span>
135
+ <CpButton appearance="primary" :color="color">Button</CpButton>
213
136
  </div>
214
137
  </div>
215
138
  `,
216
139
  }),
217
- parameters: {
218
- controls: { disable: true },
219
- docs: {
220
- source: {
221
- code: null,
222
- },
223
- },
224
- },
225
140
  }
226
141
 
227
- export const Default: Story = {
228
- args: {},
229
- render: defaultRender,
142
+ /* -------------------------------------------------------------------------- */
143
+ /* Sizes */
144
+ /* -------------------------------------------------------------------------- */
145
+
146
+ /**
147
+ * All sizes rendered side by side, from `2xs` to `lg`.
148
+ */
149
+ export const Sizes: Story = {
150
+ parameters: { controls: { disable: true } },
151
+ render: () => ({
152
+ components: { CpButton },
153
+ setup() {
154
+ return { buttonSizes, docCellStyle, docLabelStyle, docRowWrapStyle }
155
+ },
156
+ template: `
157
+ <div :style="docRowWrapStyle">
158
+ <div v-for="size in buttonSizes" :key="size" :style="docCellStyle">
159
+ <span :style="docLabelStyle">{{ size }}</span>
160
+ <CpButton :size="size" color="accent">Button</CpButton>
161
+ </div>
162
+ </div>
163
+ `,
164
+ }),
230
165
  }
231
166
 
232
- /** Semantic colors shown in appearance variant stories (primary, secondary, etc.). */
233
- const semanticButtonColors = [
234
- { value: 'neutral', label: 'neutral' },
235
- { value: 'accent', label: 'accent' },
236
- { value: 'error', label: 'error' },
237
- { value: 'warning', label: 'warning' },
238
- { value: 'success', label: 'success' },
239
- ] as const
167
+ /* -------------------------------------------------------------------------- */
168
+ /* States */
169
+ /* -------------------------------------------------------------------------- */
240
170
 
241
- const appearanceVariantsStoryParameters = {
242
- docs: {
243
- source: {
244
- code: null,
171
+ /**
172
+ * Enabled, disabled and loading states compared side by side. The loading
173
+ * state is automatically disabled to prevent double submissions.
174
+ */
175
+ export const States: Story = {
176
+ parameters: { controls: { disable: true } },
177
+ render: () => ({
178
+ components: { CpButton },
179
+ setup() {
180
+ return { docCellStyle, docLabelStyle, docRowWrapStyle }
245
181
  },
246
- },
247
- } as const
182
+ template: `
183
+ <div :style="docRowWrapStyle">
184
+ <div :style="docCellStyle">
185
+ <span :style="docLabelStyle">Enabled</span>
186
+ <CpButton color="accent">Button</CpButton>
187
+ </div>
188
+ <div :style="docCellStyle">
189
+ <span :style="docLabelStyle">Disabled</span>
190
+ <CpButton color="accent" :disabled="true">Button</CpButton>
191
+ </div>
192
+ <div :style="docCellStyle">
193
+ <span :style="docLabelStyle">Loading</span>
194
+ <CpButton color="accent" :is-loading="true">Button</CpButton>
195
+ </div>
196
+ </div>
197
+ `,
198
+ }),
199
+ }
200
+
201
+ /* -------------------------------------------------------------------------- */
202
+ /* Square vs rounded */
203
+ /* -------------------------------------------------------------------------- */
248
204
 
249
- function createAppearanceVariantsStory(appearance: ButtonAppearances): Story {
250
- return {
251
- args: {
252
- ...Default.args,
253
- appearance,
205
+ /**
206
+ * Fully rounded (default) compared with the square variant. Square fits
207
+ * better inside forms or grouped controls.
208
+ */
209
+ export const Squared: Story = {
210
+ parameters: { controls: { disable: true } },
211
+ render: () => ({
212
+ components: { CpButton },
213
+ setup() {
214
+ return { docCellStyle, docLabelStyle, docRowWrapStyle }
254
215
  },
255
- render: (args) => ({
256
- components: { CpButton },
257
- setup: () => ({
258
- args,
259
- semanticButtonColors,
260
- storyAppearance: appearance,
261
- }),
262
- template: `
263
- <div style="display: flex; flex-direction: column; gap: 32px; padding: 24px;">
264
- <div
265
- v-for="c in semanticButtonColors"
266
- :key="c.value"
267
- style="display: flex; flex-direction: column; gap: 12px; padding: 16px; background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);"
268
- >
269
- <h4 style="margin: 0; font-size: 14px; font-weight: 600; color: #333; text-transform: capitalize;">{{ c.label }}</h4>
270
- <div style="display: flex; flex-wrap: wrap; gap: 24px 32px; align-items: flex-end;">
271
- <div style="display: flex; flex-direction: column; gap: 12px;">
272
- <span style="font-size: 12px; color: #6b7280; font-weight: 500;">Square</span>
273
- <div style="display: flex; gap: 16px; align-items: flex-end;">
274
- <div style="display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
275
- <span style="font-size: 12px; color: #9ca3af;">Normal</span>
276
- <CpButton
277
- v-bind="args"
278
- :appearance="storyAppearance"
279
- :color="c.value"
280
- :is-square="true"
281
- :disabled="false"
282
- >
283
- Default button
284
- </CpButton>
285
- </div>
286
- <div style="display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
287
- <span style="font-size: 12px; color: #9ca3af;">Disabled</span>
288
- <CpButton
289
- v-bind="args"
290
- :appearance="storyAppearance"
291
- :color="c.value"
292
- :is-square="true"
293
- :disabled="true"
294
- >
295
- Default button
296
- </CpButton>
297
- </div>
298
- </div>
299
- </div>
300
- <div style="display: flex; flex-direction: column; gap: 12px;">
301
- <span style="font-size: 12px; color: #6b7280; font-weight: 500;">Not square</span>
302
- <div style="display: flex; gap: 16px; align-items: flex-end;">
303
- <div style="display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
304
- <span style="font-size: 12px; color: #9ca3af;">Normal</span>
305
- <CpButton
306
- v-bind="args"
307
- :appearance="storyAppearance"
308
- :color="c.value"
309
- :is-square="false"
310
- :disabled="false"
311
- >
312
- Default button
313
- </CpButton>
314
- </div>
315
- <div style="display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
316
- <span style="font-size: 12px; color: #9ca3af;">Disabled</span>
317
- <CpButton
318
- v-bind="args"
319
- :appearance="storyAppearance"
320
- :color="c.value"
321
- :is-square="false"
322
- :disabled="true"
323
- >
324
- Default button
325
- </CpButton>
326
- </div>
327
- </div>
328
- </div>
329
- </div>
216
+ template: `
217
+ <div :style="docRowWrapStyle">
218
+ <div :style="docCellStyle">
219
+ <span :style="docLabelStyle">Rounded</span>
220
+ <CpButton color="accent" :is-square="false">Button</CpButton>
221
+ </div>
222
+ <div :style="docCellStyle">
223
+ <span :style="docLabelStyle">Square</span>
224
+ <CpButton color="accent" :is-square="true">Button</CpButton>
330
225
  </div>
331
226
  </div>
332
227
  `,
333
- }),
334
- parameters: appearanceVariantsStoryParameters,
335
- }
228
+ }),
336
229
  }
337
230
 
338
- export const Primary = createAppearanceVariantsStory('primary')
339
- export const Secondary = createAppearanceVariantsStory('secondary')
340
- export const Tertiary = createAppearanceVariantsStory('tertiary')
341
-
342
- const withIconsSizes = [
343
- { value: '2xs', label: '2xs' },
344
- { value: 'xs', label: 'xs' },
345
- { value: 'sm', label: 'sm' },
346
- { value: 'md', label: 'md' },
347
- { value: 'lg', label: 'lg' },
348
- ] as const
231
+ /* -------------------------------------------------------------------------- */
232
+ /* Icons */
233
+ /* -------------------------------------------------------------------------- */
349
234
 
350
- export const WithIcons: Story = {
351
- args: {
352
- ...Default.args,
353
- appearance: 'primary',
354
- color: 'accent',
355
- },
356
- render: (args) => ({
235
+ /**
236
+ * Button with a leading icon, a trailing icon, both or an icon-only
237
+ * configuration (pair with `isSquare` for best visual balance).
238
+ */
239
+ export const Icons: Story = {
240
+ parameters: { controls: { disable: true } },
241
+ render: () => ({
357
242
  components: { CpButton, CpIcon },
358
- setup: () => ({ args, withIconsSizes }),
243
+ setup() {
244
+ return { docCellStyle, docLabelStyle, docRowWrapStyle }
245
+ },
359
246
  template: `
360
- <div style="display: flex; flex-wrap: wrap; gap: 24px 32px; align-items: flex-end; padding: 24px;">
361
- <div
362
- v-for="s in withIconsSizes"
363
- :key="s.value"
364
- style="display: flex; flex-direction: column; gap: 8px; align-items: flex-start;"
365
- >
366
- <span style="font-size: 12px; color: #6b7280;">{{ s.label }}</span>
367
- <CpButton v-bind="args" :size="s.value">
368
- <template #leading-icon>
369
- <CpIcon type="arrow-left" />
370
- </template>
371
- Button with Icon
372
- <template #trailing-icon>
373
- <CpIcon type="arrow-right" />
374
- </template>
247
+ <div :style="docRowWrapStyle">
248
+ <div :style="docCellStyle">
249
+ <span :style="docLabelStyle">Leading</span>
250
+ <CpButton color="accent">
251
+ <template #leading-icon><CpIcon type="plus" /></template>
252
+ Button
253
+ </CpButton>
254
+ </div>
255
+ <div :style="docCellStyle">
256
+ <span :style="docLabelStyle">Trailing</span>
257
+ <CpButton color="accent">
258
+ Button
259
+ <template #trailing-icon><CpIcon type="arrow-right" /></template>
260
+ </CpButton>
261
+ </div>
262
+ <div :style="docCellStyle">
263
+ <span :style="docLabelStyle">Both</span>
264
+ <CpButton color="accent">
265
+ <template #leading-icon><CpIcon type="arrow-left" /></template>
266
+ Button
267
+ <template #trailing-icon><CpIcon type="arrow-right" /></template>
268
+ </CpButton>
269
+ </div>
270
+ <div :style="docCellStyle">
271
+ <span :style="docLabelStyle">Icon only</span>
272
+ <CpButton color="accent" :is-square="true">
273
+ <template #leading-icon><CpIcon type="plus" /></template>
375
274
  </CpButton>
376
275
  </div>
377
276
  </div>
@@ -379,150 +278,82 @@ export const WithIcons: Story = {
379
278
  }),
380
279
  }
381
280
 
382
- export const WithHaptics: Story = {
383
- args: {
384
- ...Default.args,
385
- enableHaptics: true,
386
- color: 'accent',
387
- },
388
- render: (args) => ({
281
+ /* -------------------------------------------------------------------------- */
282
+ /* Element & form integration */
283
+ /* -------------------------------------------------------------------------- */
284
+
285
+ /**
286
+ * Render the button as an `<a>` anchor while keeping the visual style.
287
+ * Useful for links that look like buttons.
288
+ */
289
+ export const AsAnchor: Story = {
290
+ args: { ...Default.args, tag: 'a', color: 'accent' },
291
+ render: (args: Args) => ({
389
292
  components: { CpButton },
390
293
  setup() {
391
- const onClick = () => {
392
- console.log('Button clicked')
393
- }
294
+ return { args }
295
+ },
296
+ template: `<CpButton v-bind="args" href="#">Anchor button</CpButton>`,
297
+ }),
298
+ }
394
299
 
395
- return { args, onClick }
300
+ /**
301
+ * `type="submit"` submits the parent form. The button is wrapped in a minimal
302
+ * form to demonstrate the behaviour.
303
+ */
304
+ export const SubmitType: Story = {
305
+ args: { ...Default.args, type: 'submit', color: 'accent' },
306
+ render: (args: Args) => ({
307
+ components: { CpButton },
308
+ setup() {
309
+ const handleSubmit = (event: Event) => {
310
+ event.preventDefault()
311
+ console.log('Form submitted')
312
+ }
313
+ return { args, handleSubmit }
396
314
  },
397
315
  template: `
398
- <CpButton v-bind="args" @click="onClick">
399
- Button with haptics
400
- </CpButton>
316
+ <form @submit="handleSubmit" style="display: inline-flex;">
317
+ <CpButton v-bind="args">Submit form</CpButton>
318
+ </form>
401
319
  `,
402
320
  }),
403
321
  }
404
322
 
405
- export const Loading: Story = {
406
- args: {
407
- ...Default.args,
408
- isLoading: true,
409
- color: 'accent',
410
- },
411
- render: defaultRender,
412
- }
413
-
414
- const iconOnlySizes = [
415
- { value: '2xs', label: '2xs' },
416
- { value: 'xs', label: 'xs' },
417
- { value: 'sm', label: 'sm' },
418
- { value: 'md', label: 'md' },
419
- { value: 'lg', label: 'lg' },
420
- ] as const
421
-
422
- /** Icon-only appearances (inverse excluded for this story). */
423
- const iconOnlyAppearances = [
424
- { value: 'primary', label: 'primary' },
425
- { value: 'secondary', label: 'secondary' },
426
- { value: 'tertiary', label: 'tertiary' },
427
- ] as const
428
-
429
- export const IconOnly: Story = {
430
- args: {
431
- ...Default.args,
432
- appearance: 'primary',
433
- color: 'accent',
434
- isSquare: true,
435
- },
436
- render: (args) => ({
437
- components: { CpButton, CpIcon },
438
- setup: () => ({
439
- args,
440
- iconOnlySizes,
441
- iconOnlyAppearances,
442
- semanticButtonColors,
443
- docSectionStyle,
444
- docTitleStyle,
445
- docRowWrapStyle,
446
- docCellStyle,
447
- docLabelStyle,
448
- }),
323
+ /**
324
+ * `type="reset"` resets the parent form to its initial values.
325
+ */
326
+ export const ResetType: Story = {
327
+ args: { ...Default.args, type: 'reset', appearance: 'secondary', color: 'neutral' },
328
+ render: (args: Args) => ({
329
+ components: { CpButton },
330
+ setup() {
331
+ return { args }
332
+ },
449
333
  template: `
450
- <div style="${docPageStyle}">
451
- <h1 style="margin: 0 0 32px 0; font-size: 28px; font-weight: 700; color: #111;">Icon Button</h1>
452
-
453
- <section style="${docSectionStyle}">
454
- <h2 style="${docTitleStyle}">Size</h2>
455
- <div style="${docRowWrapStyle}">
456
- <div
457
- v-for="s in iconOnlySizes"
458
- :key="s.value"
459
- style="${docCellStyle}"
460
- >
461
- <span style="${docLabelStyle}">{{ s.label }}</span>
462
- <CpButton v-bind="args" :size="s.value">
463
- <template #leading-icon><CpIcon type="plus" /></template>
464
- </CpButton>
465
- </div>
466
- </div>
467
- </section>
468
-
469
- <section style="${docSectionStyle}">
470
- <h2 style="${docTitleStyle}">Color</h2>
471
- <div style="${docRowWrapStyle}">
472
- <div
473
- v-for="c in semanticButtonColors"
474
- :key="c.value"
475
- style="${docCellStyle}"
476
- >
477
- <span style="${docLabelStyle}">{{ c.label }}</span>
478
- <CpButton v-bind="args" :color="c.value">
479
- <template #leading-icon><CpIcon type="plus" /></template>
480
- </CpButton>
481
- </div>
482
- </div>
483
- </section>
484
-
485
- <section style="${docSectionStyle}">
486
- <h2 style="${docTitleStyle}">Squared</h2>
487
- <div style="${docRowWrapStyle}">
488
- <div style="${docCellStyle}">
489
- <span style="${docLabelStyle}">false</span>
490
- <CpButton v-bind="args" :is-square="false">
491
- <template #leading-icon><CpIcon type="plus" /></template>
492
- </CpButton>
493
- </div>
494
- <div style="${docCellStyle}">
495
- <span style="${docLabelStyle}">true</span>
496
- <CpButton v-bind="args" :is-square="true">
497
- <template #leading-icon><CpIcon type="plus" /></template>
498
- </CpButton>
499
- </div>
500
- </div>
501
- </section>
502
-
503
- <section style="${docSectionStyle}">
504
- <h2 style="${docTitleStyle}">Style</h2>
505
- <div style="${docRowWrapStyle}">
506
- <div
507
- v-for="a in iconOnlyAppearances"
508
- :key="a.value"
509
- style="${docCellStyle}"
510
- >
511
- <span style="${docLabelStyle}">{{ a.label }}</span>
512
- <CpButton v-bind="args" :appearance="a.value">
513
- <template #leading-icon><CpIcon type="plus" /></template>
514
- </CpButton>
515
- </div>
516
- </div>
517
- </section>
518
- </div>
334
+ <form style="display: inline-flex; gap: 8px;">
335
+ <input name="field" defaultValue="" placeholder="Type, then reset" style="padding: 8px; border: 1px solid #e5e7eb; border-radius: 6px;" />
336
+ <CpButton v-bind="args">Reset</CpButton>
337
+ </form>
519
338
  `,
520
339
  }),
521
- parameters: {
522
- docs: {
523
- source: {
524
- code: null,
525
- },
340
+ }
341
+
342
+ /* -------------------------------------------------------------------------- */
343
+ /* Interaction */
344
+ /* -------------------------------------------------------------------------- */
345
+
346
+ /**
347
+ * Triggers a haptic feedback on supported devices on click.
348
+ */
349
+ export const WithHaptics: Story = {
350
+ args: { ...Default.args, enableHaptics: true, color: 'accent' },
351
+ render: (args: Args) => ({
352
+ components: { CpButton },
353
+ setup() {
354
+ const onClick = () => console.log('Button clicked')
355
+ return { args, onClick }
526
356
  },
527
- },
357
+ template: `<CpButton v-bind="args" @click="onClick">Button with haptics</CpButton>`,
358
+ }),
528
359
  }