@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.
- package/dist/pimp.es.js +313 -285
- package/dist/pimp.umd.js +21 -21
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/CpHeading.vue +4 -5
- package/src/components/CpText.vue +141 -0
- package/src/components/index.ts +2 -0
- package/src/stories/BaseInputLabel.stories.ts +36 -9
- package/src/stories/Colors.mdx +9 -0
- package/src/stories/Colors.stories.ts +177 -0
- package/src/stories/CpAccordion.stories.ts +187 -158
- package/src/stories/CpAccordionGroup.stories.ts +50 -94
- package/src/stories/CpAirlineLogo.stories.ts +49 -28
- package/src/stories/CpAlert.stories.ts +195 -158
- package/src/stories/CpBadge.stories.ts +259 -193
- package/src/stories/CpButton.stories.ts +257 -426
- package/src/stories/CpCheckbox.stories.ts +101 -29
- package/src/stories/CpContextualMenu.stories.ts +9 -8
- package/src/stories/CpDate.stories.ts +52 -25
- package/src/stories/CpDatepicker.stories.ts +57 -88
- package/src/stories/CpDialog.stories.ts +22 -1
- package/src/stories/CpHeading.stories.ts +59 -20
- package/src/stories/CpIcon.stories.ts +98 -31
- package/src/stories/CpInput.stories.ts +142 -67
- package/src/stories/CpItemActions.stories.ts +22 -27
- package/src/stories/CpLoader.stories.ts +54 -6
- package/src/stories/CpMenuItem.stories.ts +52 -26
- package/src/stories/CpMultiselect.stories.ts +52 -71
- package/src/stories/CpPartnerBadge.stories.ts +53 -74
- package/src/stories/CpRadio.stories.ts +44 -48
- package/src/stories/CpRadioGroup.stories.ts +46 -39
- package/src/stories/CpSelect.stories.ts +98 -39
- package/src/stories/CpSelectMenu.stories.ts +49 -57
- package/src/stories/CpSelectableButton.stories.ts +170 -81
- package/src/stories/CpSwitch.stories.ts +135 -133
- package/src/stories/CpTable.stories.ts +54 -1
- package/src/stories/CpTableEmptyState.stories.ts +11 -7
- package/src/stories/CpTabs.stories.ts +22 -4
- package/src/stories/CpTelInput.stories.ts +25 -23
- package/src/stories/CpText.stories.ts +131 -0
- package/src/stories/CpTextarea.stories.ts +59 -23
- package/src/stories/CpToast.stories.ts +53 -103
- package/src/stories/CpTooltip.stories.ts +82 -77
- package/src/stories/CpTransitionCounter.stories.ts +4 -0
- package/src/stories/CpTransitionExpand.stories.ts +11 -6
- package/src/stories/CpTransitionListItems.stories.ts +5 -0
- package/src/stories/CpTransitionSize.stories.ts +8 -0
- package/src/stories/CpTransitionSlide.stories.ts +4 -0
- package/src/stories/CpTransitionTabContent.stories.ts +4 -0
- package/src/stories/Dimensions.mdx +9 -0
- package/src/stories/Dimensions.stories.ts +119 -0
- package/src/stories/Easings.mdx +9 -0
- package/src/stories/Easings.stories.ts +101 -0
- package/src/stories/FocusRings.mdx +9 -0
- package/src/stories/FocusRings.stories.ts +74 -0
- package/src/stories/Shadows.mdx +9 -0
- package/src/stories/Shadows.stories.ts +100 -0
- package/src/stories/Typography.mdx +9 -0
- package/src/stories/Typography.stories.ts +181 -0
- package/src/stories/documentationStyles.ts +2 -10
- package/src/stories/tokenUtils.ts +259 -0
|
@@ -4,8 +4,10 @@ import type { Meta, StoryObj } from '@storybook/vue3'
|
|
|
4
4
|
|
|
5
5
|
import CpRadio from '@/components/CpRadio.vue'
|
|
6
6
|
|
|
7
|
+
const radioColors = ['accent', 'blue'] as const
|
|
8
|
+
|
|
7
9
|
const meta = {
|
|
8
|
-
title: '
|
|
10
|
+
title: 'Atoms/CpRadio',
|
|
9
11
|
component: CpRadio,
|
|
10
12
|
argTypes: {
|
|
11
13
|
modelValue: {
|
|
@@ -22,7 +24,7 @@ const meta = {
|
|
|
22
24
|
},
|
|
23
25
|
color: {
|
|
24
26
|
control: 'select',
|
|
25
|
-
options:
|
|
27
|
+
options: radioColors,
|
|
26
28
|
description: 'Color variant of the radio',
|
|
27
29
|
},
|
|
28
30
|
autofocus: {
|
|
@@ -41,6 +43,23 @@ const sampleOptions = [
|
|
|
41
43
|
{ value: 'option3', label: 'Option 3' },
|
|
42
44
|
]
|
|
43
45
|
|
|
46
|
+
const wrappedRender = (args: { modelValue?: string }) => ({
|
|
47
|
+
components: { CpRadio },
|
|
48
|
+
setup() {
|
|
49
|
+
const value = ref(args.modelValue)
|
|
50
|
+
return { args, value }
|
|
51
|
+
},
|
|
52
|
+
template: `
|
|
53
|
+
<div style="min-width: 400px; padding: 20px;">
|
|
54
|
+
<CpRadio v-model="value" v-bind="args" />
|
|
55
|
+
</div>
|
|
56
|
+
`,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default radio group with three plain options. Use the controls to
|
|
61
|
+
* experiment with each prop in isolation.
|
|
62
|
+
*/
|
|
44
63
|
export const Default: Story = {
|
|
45
64
|
args: {
|
|
46
65
|
modelValue: 'option1',
|
|
@@ -49,23 +68,13 @@ export const Default: Story = {
|
|
|
49
68
|
color: 'accent',
|
|
50
69
|
autofocus: false,
|
|
51
70
|
},
|
|
52
|
-
render:
|
|
53
|
-
components: { CpRadio },
|
|
54
|
-
setup() {
|
|
55
|
-
const value = ref(args.modelValue)
|
|
56
|
-
return { args, value }
|
|
57
|
-
},
|
|
58
|
-
template: `
|
|
59
|
-
<div style="min-width: 400px; padding: 20px;">
|
|
60
|
-
<CpRadio
|
|
61
|
-
v-model="value"
|
|
62
|
-
v-bind="args"
|
|
63
|
-
/>
|
|
64
|
-
</div>
|
|
65
|
-
`,
|
|
66
|
-
}),
|
|
71
|
+
render: wrappedRender,
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Each option can provide a secondary `description` displayed under the
|
|
76
|
+
* label — great for presenting plans or multi-line choices.
|
|
77
|
+
*/
|
|
69
78
|
export const WithDescriptions: Story = {
|
|
70
79
|
args: {
|
|
71
80
|
...Default.args,
|
|
@@ -77,6 +86,10 @@ export const WithDescriptions: Story = {
|
|
|
77
86
|
},
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Append a right-aligned piece of information through `additionalData`
|
|
91
|
+
* (e.g. price, unit, shortcut).
|
|
92
|
+
*/
|
|
80
93
|
export const WithAdditionalData: Story = {
|
|
81
94
|
args: {
|
|
82
95
|
...Default.args,
|
|
@@ -88,7 +101,11 @@ export const WithAdditionalData: Story = {
|
|
|
88
101
|
},
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Disable individual options using their `disabled` flag — the value stays
|
|
106
|
+
* selectable on other rows.
|
|
107
|
+
*/
|
|
108
|
+
export const WithDisabledOption: Story = {
|
|
92
109
|
args: {
|
|
93
110
|
...Default.args,
|
|
94
111
|
options: [
|
|
@@ -97,23 +114,13 @@ export const WithDisabledOptions: Story = {
|
|
|
97
114
|
{ value: 'option3', label: 'Option 3' },
|
|
98
115
|
],
|
|
99
116
|
},
|
|
100
|
-
render:
|
|
101
|
-
components: { CpRadio },
|
|
102
|
-
setup() {
|
|
103
|
-
const value = ref(args.modelValue)
|
|
104
|
-
return { args, value }
|
|
105
|
-
},
|
|
106
|
-
template: `
|
|
107
|
-
<div style="min-width: 400px; padding: 20px;">
|
|
108
|
-
<CpRadio
|
|
109
|
-
v-model="value"
|
|
110
|
-
v-bind="args"
|
|
111
|
-
/>
|
|
112
|
-
</div>
|
|
113
|
-
`,
|
|
114
|
-
}),
|
|
117
|
+
render: wrappedRender,
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
/**
|
|
121
|
+
* A disabled option can still be the current value — useful when the user
|
|
122
|
+
* cannot change a pre-selected choice.
|
|
123
|
+
*/
|
|
117
124
|
export const CheckedDisabled: Story = {
|
|
118
125
|
args: {
|
|
119
126
|
...Default.args,
|
|
@@ -124,23 +131,12 @@ export const CheckedDisabled: Story = {
|
|
|
124
131
|
],
|
|
125
132
|
modelValue: 'option1',
|
|
126
133
|
},
|
|
127
|
-
render:
|
|
128
|
-
components: { CpRadio },
|
|
129
|
-
setup() {
|
|
130
|
-
const value = ref(args.modelValue)
|
|
131
|
-
return { args, value }
|
|
132
|
-
},
|
|
133
|
-
template: `
|
|
134
|
-
<div style="min-width: 400px; padding: 20px;">
|
|
135
|
-
<CpRadio
|
|
136
|
-
v-model="value"
|
|
137
|
-
v-bind="args"
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
`,
|
|
141
|
-
}),
|
|
134
|
+
render: wrappedRender,
|
|
142
135
|
}
|
|
143
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Combine `description` and `additionalData` for rich option rows.
|
|
139
|
+
*/
|
|
144
140
|
export const ComplexOptions: Story = {
|
|
145
141
|
args: {
|
|
146
142
|
...Default.args,
|
|
@@ -4,8 +4,11 @@ import type { Meta, StoryObj } from '@storybook/vue3'
|
|
|
4
4
|
|
|
5
5
|
import CpRadioGroup from '@/components/CpRadioGroup.vue'
|
|
6
6
|
|
|
7
|
+
const radioGroupSizes = ['md', 'lg'] as const
|
|
8
|
+
const radioGroupDirections = ['vertical', 'horizontal'] as const
|
|
9
|
+
|
|
7
10
|
const meta = {
|
|
8
|
-
title: '
|
|
11
|
+
title: 'Molecules/CpRadioGroup',
|
|
9
12
|
component: CpRadioGroup,
|
|
10
13
|
argTypes: {
|
|
11
14
|
modelValue: {
|
|
@@ -26,7 +29,7 @@ const meta = {
|
|
|
26
29
|
},
|
|
27
30
|
direction: {
|
|
28
31
|
control: 'select',
|
|
29
|
-
options:
|
|
32
|
+
options: radioGroupDirections,
|
|
30
33
|
description: 'Stack options vertically or place them in a row',
|
|
31
34
|
},
|
|
32
35
|
groupLabel: {
|
|
@@ -43,7 +46,7 @@ const meta = {
|
|
|
43
46
|
},
|
|
44
47
|
size: {
|
|
45
48
|
control: 'select',
|
|
46
|
-
options:
|
|
49
|
+
options: radioGroupSizes,
|
|
47
50
|
description: 'The size of the radio',
|
|
48
51
|
},
|
|
49
52
|
},
|
|
@@ -58,6 +61,23 @@ const sampleOptions = [
|
|
|
58
61
|
{ value: 'option3', label: 'Option 3' },
|
|
59
62
|
]
|
|
60
63
|
|
|
64
|
+
const wrappedRender = (args: { modelValue?: string }) => ({
|
|
65
|
+
components: { CpRadioGroup },
|
|
66
|
+
setup() {
|
|
67
|
+
const value = ref(args.modelValue)
|
|
68
|
+
return { args, value }
|
|
69
|
+
},
|
|
70
|
+
template: `
|
|
71
|
+
<div style="min-width: 800px; padding: 20px;">
|
|
72
|
+
<CpRadioGroup v-model="value" v-bind="args" />
|
|
73
|
+
</div>
|
|
74
|
+
`,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Default radio group, stacked vertically. Use the controls to experiment
|
|
79
|
+
* with each prop in isolation.
|
|
80
|
+
*/
|
|
61
81
|
export const Default: Story = {
|
|
62
82
|
args: {
|
|
63
83
|
modelValue: 'option1',
|
|
@@ -78,16 +98,16 @@ export const Default: Story = {
|
|
|
78
98
|
},
|
|
79
99
|
template: `
|
|
80
100
|
<div style="min-width: 800px; padding: 20px;">
|
|
81
|
-
<CpRadioGroup
|
|
82
|
-
|
|
83
|
-
v-bind="args"
|
|
84
|
-
/>
|
|
85
|
-
<p style="margin-top: 24px;font-size:12px;">modelValue: <code>{{ value }}</code></p>
|
|
101
|
+
<CpRadioGroup v-model="value" v-bind="args" />
|
|
102
|
+
<p style="margin-top: 24px; font-size: 12px;">modelValue: <code>{{ value }}</code></p>
|
|
86
103
|
</div>
|
|
87
104
|
`,
|
|
88
105
|
}),
|
|
89
106
|
}
|
|
90
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Arrange the options on a single row with `direction="horizontal"`.
|
|
110
|
+
*/
|
|
91
111
|
export const Horizontal: Story = {
|
|
92
112
|
args: {
|
|
93
113
|
...Default.args,
|
|
@@ -95,6 +115,10 @@ export const Horizontal: Story = {
|
|
|
95
115
|
},
|
|
96
116
|
}
|
|
97
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Add a visible label and helper text describing the whole group. Setting
|
|
120
|
+
* `required` puts the asterisk on the group label.
|
|
121
|
+
*/
|
|
98
122
|
export const WithGroupLabel: Story = {
|
|
99
123
|
args: {
|
|
100
124
|
...Default.args,
|
|
@@ -104,6 +128,10 @@ export const WithGroupLabel: Story = {
|
|
|
104
128
|
},
|
|
105
129
|
}
|
|
106
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Attach a `helperText` to individual options — useful for short
|
|
133
|
+
* explanations next to each choice.
|
|
134
|
+
*/
|
|
107
135
|
export const WithHelperText: Story = {
|
|
108
136
|
args: {
|
|
109
137
|
...Default.args,
|
|
@@ -115,7 +143,10 @@ export const WithHelperText: Story = {
|
|
|
115
143
|
},
|
|
116
144
|
}
|
|
117
145
|
|
|
118
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Disable individual options; the rest of the group stays selectable.
|
|
148
|
+
*/
|
|
149
|
+
export const WithDisabledOption: Story = {
|
|
119
150
|
args: {
|
|
120
151
|
...Default.args,
|
|
121
152
|
options: [
|
|
@@ -124,23 +155,13 @@ export const WithDisabledOptions: Story = {
|
|
|
124
155
|
{ value: 'option3', label: 'Option 3' },
|
|
125
156
|
],
|
|
126
157
|
},
|
|
127
|
-
render:
|
|
128
|
-
components: { CpRadioGroup },
|
|
129
|
-
setup() {
|
|
130
|
-
const value = ref(args.modelValue)
|
|
131
|
-
return { args, value }
|
|
132
|
-
},
|
|
133
|
-
template: `
|
|
134
|
-
<div style="min-width: 800px; padding: 20px;">
|
|
135
|
-
<CpRadioGroup
|
|
136
|
-
v-model="value"
|
|
137
|
-
v-bind="args"
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
`,
|
|
141
|
-
}),
|
|
158
|
+
render: wrappedRender,
|
|
142
159
|
}
|
|
143
160
|
|
|
161
|
+
/**
|
|
162
|
+
* A disabled option can still be the current value. Use this pattern for
|
|
163
|
+
* read-only pre-selections.
|
|
164
|
+
*/
|
|
144
165
|
export const CheckedDisabled: Story = {
|
|
145
166
|
args: {
|
|
146
167
|
...Default.args,
|
|
@@ -151,19 +172,5 @@ export const CheckedDisabled: Story = {
|
|
|
151
172
|
],
|
|
152
173
|
modelValue: 'option1',
|
|
153
174
|
},
|
|
154
|
-
render:
|
|
155
|
-
components: { CpRadioGroup },
|
|
156
|
-
setup() {
|
|
157
|
-
const value = ref(args.modelValue)
|
|
158
|
-
return { args, value }
|
|
159
|
-
},
|
|
160
|
-
template: `
|
|
161
|
-
<div style="min-width: 800px; padding: 20px;">
|
|
162
|
-
<CpRadioGroup
|
|
163
|
-
v-model="value"
|
|
164
|
-
v-bind="args"
|
|
165
|
-
/>
|
|
166
|
-
</div>
|
|
167
|
-
`,
|
|
168
|
-
}),
|
|
175
|
+
render: wrappedRender,
|
|
169
176
|
}
|
|
@@ -5,8 +5,14 @@ import type { Meta, StoryObj } from '@storybook/vue3'
|
|
|
5
5
|
import CpInput from '@/components/CpInput.vue'
|
|
6
6
|
import CpSelect from '@/components/CpSelect.vue'
|
|
7
7
|
|
|
8
|
+
import { docCellStyle, docLabelStyle, docRowColumnStyle } from '@/stories/documentationStyles'
|
|
9
|
+
|
|
10
|
+
const selectSizes = ['sm', 'md', 'lg'] as const
|
|
11
|
+
|
|
12
|
+
const selectStackStyle = `${docCellStyle} width: 100%; max-width: 360px;`
|
|
13
|
+
|
|
8
14
|
const meta = {
|
|
9
|
-
title: '
|
|
15
|
+
title: 'Molecules/CpSelect',
|
|
10
16
|
component: CpSelect,
|
|
11
17
|
argTypes: {
|
|
12
18
|
modelValue: {
|
|
@@ -47,7 +53,7 @@ const meta = {
|
|
|
47
53
|
},
|
|
48
54
|
size: {
|
|
49
55
|
control: 'select',
|
|
50
|
-
options:
|
|
56
|
+
options: selectSizes,
|
|
51
57
|
description: 'The size of the select',
|
|
52
58
|
},
|
|
53
59
|
autocomplete: {
|
|
@@ -79,6 +85,9 @@ const sampleOptions = [
|
|
|
79
85
|
{ value: '4', label: 'Option 4' },
|
|
80
86
|
]
|
|
81
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Default select. Use the controls to experiment with each prop in isolation.
|
|
90
|
+
*/
|
|
82
91
|
export const Default: Story = {
|
|
83
92
|
args: {
|
|
84
93
|
label: 'Select Label',
|
|
@@ -101,51 +110,97 @@ export const Default: Story = {
|
|
|
101
110
|
},
|
|
102
111
|
template: `
|
|
103
112
|
<div style="max-width: 400px; padding: 20px;">
|
|
104
|
-
<CpSelect
|
|
105
|
-
v-model="value"
|
|
106
|
-
v-bind="args"
|
|
107
|
-
/>
|
|
113
|
+
<CpSelect v-model="value" v-bind="args" />
|
|
108
114
|
</div>
|
|
109
115
|
`,
|
|
110
116
|
}),
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
isInvalid: true,
|
|
117
|
-
errorMessage: 'This field is required',
|
|
118
|
-
},
|
|
119
|
-
}
|
|
119
|
+
/* -------------------------------------------------------------------------- */
|
|
120
|
+
/* Sizes */
|
|
121
|
+
/* -------------------------------------------------------------------------- */
|
|
120
122
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
},
|
|
123
|
+
/**
|
|
124
|
+
* All sizes rendered side by side, from `sm` to `lg`.
|
|
125
|
+
*/
|
|
126
|
+
export const Sizes: Story = {
|
|
127
|
+
parameters: { controls: { disable: true } },
|
|
128
|
+
render: () => ({
|
|
129
|
+
components: { CpSelect },
|
|
130
|
+
setup() {
|
|
131
|
+
const value = ref('3')
|
|
132
|
+
return { value, sampleOptions, selectSizes, docRowColumnStyle, selectStackStyle, docLabelStyle }
|
|
133
|
+
},
|
|
134
|
+
template: `
|
|
135
|
+
<div :style="docRowColumnStyle">
|
|
136
|
+
<div v-for="size in selectSizes" :key="size" :style="selectStackStyle">
|
|
137
|
+
<span :style="docLabelStyle">{{ size }}</span>
|
|
138
|
+
<CpSelect
|
|
139
|
+
v-model="value"
|
|
140
|
+
:size="size"
|
|
141
|
+
label="Select label"
|
|
142
|
+
:options="sampleOptions"
|
|
143
|
+
name="select-field"
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
`,
|
|
148
|
+
}),
|
|
126
149
|
}
|
|
127
150
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
disabled: true,
|
|
132
|
-
},
|
|
133
|
-
}
|
|
151
|
+
/* -------------------------------------------------------------------------- */
|
|
152
|
+
/* States */
|
|
153
|
+
/* -------------------------------------------------------------------------- */
|
|
134
154
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
},
|
|
155
|
+
/**
|
|
156
|
+
* Default, required, disabled and invalid states compared side by side.
|
|
157
|
+
*/
|
|
158
|
+
export const States: Story = {
|
|
159
|
+
parameters: { controls: { disable: true } },
|
|
160
|
+
render: () => ({
|
|
161
|
+
components: { CpSelect },
|
|
162
|
+
setup() {
|
|
163
|
+
const value = ref('3')
|
|
164
|
+
return { value, sampleOptions, docRowColumnStyle, selectStackStyle, docLabelStyle }
|
|
165
|
+
},
|
|
166
|
+
template: `
|
|
167
|
+
<div :style="docRowColumnStyle">
|
|
168
|
+
<div :style="selectStackStyle">
|
|
169
|
+
<span :style="docLabelStyle">Default</span>
|
|
170
|
+
<CpSelect v-model="value" label="Select label" :options="sampleOptions" name="s-default" />
|
|
171
|
+
</div>
|
|
172
|
+
<div :style="selectStackStyle">
|
|
173
|
+
<span :style="docLabelStyle">Required</span>
|
|
174
|
+
<CpSelect v-model="value" label="Select label" :options="sampleOptions" :required="true" name="s-required" />
|
|
175
|
+
</div>
|
|
176
|
+
<div :style="selectStackStyle">
|
|
177
|
+
<span :style="docLabelStyle">Disabled</span>
|
|
178
|
+
<CpSelect v-model="value" label="Select label" :options="sampleOptions" :disabled="true" name="s-disabled" />
|
|
179
|
+
</div>
|
|
180
|
+
<div :style="selectStackStyle">
|
|
181
|
+
<span :style="docLabelStyle">Invalid</span>
|
|
182
|
+
<CpSelect
|
|
183
|
+
v-model="value"
|
|
184
|
+
label="Select label"
|
|
185
|
+
:options="sampleOptions"
|
|
186
|
+
:is-invalid="true"
|
|
187
|
+
error-message="This field is required"
|
|
188
|
+
name="s-invalid"
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
`,
|
|
193
|
+
}),
|
|
140
194
|
}
|
|
141
195
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
size: 'lg',
|
|
146
|
-
},
|
|
147
|
-
}
|
|
196
|
+
/* -------------------------------------------------------------------------- */
|
|
197
|
+
/* Misc */
|
|
198
|
+
/* -------------------------------------------------------------------------- */
|
|
148
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Hides the default "Select an option" entry. Use this when an option must
|
|
202
|
+
* always be picked.
|
|
203
|
+
*/
|
|
149
204
|
export const WithoutDefaultOption: Story = {
|
|
150
205
|
args: {
|
|
151
206
|
...Default.args,
|
|
@@ -153,6 +208,9 @@ export const WithoutDefaultOption: Story = {
|
|
|
153
208
|
},
|
|
154
209
|
}
|
|
155
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Long option labels are truncated with an ellipsis.
|
|
213
|
+
*/
|
|
156
214
|
export const WithLongOptions: Story = {
|
|
157
215
|
args: {
|
|
158
216
|
...Default.args,
|
|
@@ -165,6 +223,10 @@ export const WithLongOptions: Story = {
|
|
|
165
223
|
},
|
|
166
224
|
}
|
|
167
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Combines a select and an input on the same line — for example a currency
|
|
228
|
+
* and an amount.
|
|
229
|
+
*/
|
|
168
230
|
export const NextToInput: Story = {
|
|
169
231
|
args: {
|
|
170
232
|
...Default.args,
|
|
@@ -180,10 +242,7 @@ export const NextToInput: Story = {
|
|
|
180
242
|
},
|
|
181
243
|
template: `
|
|
182
244
|
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 10px; max-width: 400px; padding: 20px;">
|
|
183
|
-
<CpSelect
|
|
184
|
-
v-model="value"
|
|
185
|
-
v-bind="args"
|
|
186
|
-
/>
|
|
245
|
+
<CpSelect v-model="value" v-bind="args" />
|
|
187
246
|
<CpInput :size="args.size" label="Input Label" name="input-field" placeholder="Enter text here" v-model="textValue" />
|
|
188
247
|
</div>
|
|
189
248
|
`,
|
|
@@ -5,39 +5,21 @@ import type { Meta, StoryObj } from '@storybook/vue3'
|
|
|
5
5
|
import CpSelectMenu from '@/components/CpSelectMenu.vue'
|
|
6
6
|
|
|
7
7
|
const meta = {
|
|
8
|
-
title: '
|
|
8
|
+
title: 'Molecules/CpSelectMenu',
|
|
9
9
|
component: CpSelectMenu,
|
|
10
10
|
argTypes: {
|
|
11
|
-
values: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
|
|
16
|
-
control: 'object',
|
|
17
|
-
description: 'Currently selected value',
|
|
18
|
-
},
|
|
19
|
-
hasFilter: {
|
|
20
|
-
control: 'boolean',
|
|
21
|
-
description: 'Whether to show search filter',
|
|
22
|
-
},
|
|
23
|
-
dropdownTitle: {
|
|
24
|
-
control: 'text',
|
|
25
|
-
description: 'Title text for the dropdown',
|
|
26
|
-
},
|
|
27
|
-
dropdownFilterPlaceholder: {
|
|
28
|
-
control: 'text',
|
|
29
|
-
description: 'Placeholder text for the filter input',
|
|
30
|
-
},
|
|
11
|
+
values: { control: 'object', description: 'Array of options to display' },
|
|
12
|
+
selectedValue: { control: 'object', description: 'Currently selected value' },
|
|
13
|
+
hasFilter: { control: 'boolean', description: 'Whether to show search filter' },
|
|
14
|
+
dropdownTitle: { control: 'text', description: 'Title text for the dropdown' },
|
|
15
|
+
dropdownFilterPlaceholder: { control: 'text', description: 'Placeholder text for the filter input' },
|
|
31
16
|
dropdownEmptyViewPlaceholder: {
|
|
32
17
|
control: 'text',
|
|
33
18
|
description: 'Text to show when no options match the filter',
|
|
34
19
|
},
|
|
35
|
-
closeOnSelect: {
|
|
36
|
-
control: 'boolean',
|
|
37
|
-
description: 'Whether to close dropdown after selection',
|
|
38
|
-
},
|
|
20
|
+
closeOnSelect: { control: 'boolean', description: 'Whether to close dropdown after selection' },
|
|
39
21
|
},
|
|
40
|
-
decorators: [() => ({ template: '<div style="min-height: 30vh;"><story/></div>' })],
|
|
22
|
+
decorators: [() => ({ template: '<div style="min-height: 30vh;"><story /></div>' })],
|
|
41
23
|
} satisfies Meta<typeof CpSelectMenu>
|
|
42
24
|
|
|
43
25
|
export default meta
|
|
@@ -53,6 +35,10 @@ const sampleOptions = [
|
|
|
53
35
|
{ value: '7', label: 'Cherry' },
|
|
54
36
|
]
|
|
55
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Default select menu. Use the controls to experiment with each prop in
|
|
40
|
+
* isolation.
|
|
41
|
+
*/
|
|
56
42
|
export const Default: Story = {
|
|
57
43
|
args: {
|
|
58
44
|
values: sampleOptions,
|
|
@@ -79,53 +65,59 @@ export const Default: Story = {
|
|
|
79
65
|
</div>
|
|
80
66
|
`,
|
|
81
67
|
methods: {
|
|
82
|
-
onUpdateSelectedValue:
|
|
68
|
+
onUpdateSelectedValue(data: unknown) {
|
|
83
69
|
args.selectedValue = data
|
|
84
70
|
},
|
|
85
71
|
},
|
|
86
72
|
}),
|
|
87
73
|
}
|
|
88
74
|
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Enable `hasFilter` to render a search input above the list. The
|
|
77
|
+
* `@on-filter-change` event lets the caller filter its own options list.
|
|
78
|
+
*/
|
|
91
79
|
export const WithFilter: Story = {
|
|
92
80
|
args: {
|
|
93
81
|
...Default.args,
|
|
94
82
|
hasFilter: true,
|
|
95
83
|
dropdownTitle: 'Search for a fruit',
|
|
96
|
-
selectedValue: { value: '1', label: 'Apple' },
|
|
97
|
-
values: sampleOptions,
|
|
98
84
|
},
|
|
99
|
-
render: (args) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<div style="max-width: 400px; padding: 20px;">
|
|
107
|
-
<CpSelectMenu
|
|
108
|
-
v-model:selectedValue="selectedValue"
|
|
109
|
-
v-bind="args"
|
|
110
|
-
@on-filter-change="onFilterChange"
|
|
111
|
-
@update:selected-value="onUpdateSelectedValue"
|
|
112
|
-
/>
|
|
113
|
-
</div>
|
|
114
|
-
`,
|
|
115
|
-
methods: {
|
|
116
|
-
onFilterChange: (data) => {
|
|
117
|
-
searchQuery.value = data
|
|
118
|
-
args.values = sampleOptions.filter((option) =>
|
|
119
|
-
option.label.toLowerCase().includes(searchQuery.value.toLowerCase()),
|
|
120
|
-
)
|
|
85
|
+
render: (args) => {
|
|
86
|
+
const searchQuery = ref('')
|
|
87
|
+
return {
|
|
88
|
+
components: { CpSelectMenu },
|
|
89
|
+
setup() {
|
|
90
|
+
const selectedValue = ref(args.selectedValue)
|
|
91
|
+
return { args, selectedValue }
|
|
121
92
|
},
|
|
122
|
-
|
|
123
|
-
|
|
93
|
+
template: `
|
|
94
|
+
<div style="max-width: 400px; padding: 20px;">
|
|
95
|
+
<CpSelectMenu
|
|
96
|
+
v-model:selectedValue="selectedValue"
|
|
97
|
+
v-bind="args"
|
|
98
|
+
@on-filter-change="onFilterChange"
|
|
99
|
+
@update:selected-value="onUpdateSelectedValue"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
`,
|
|
103
|
+
methods: {
|
|
104
|
+
onFilterChange(data: string) {
|
|
105
|
+
searchQuery.value = data
|
|
106
|
+
args.values = sampleOptions.filter((option) =>
|
|
107
|
+
option.label.toLowerCase().includes(searchQuery.value.toLowerCase()),
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
onUpdateSelectedValue(data: unknown) {
|
|
111
|
+
args.selectedValue = data
|
|
112
|
+
},
|
|
124
113
|
},
|
|
125
|
-
}
|
|
126
|
-
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
127
116
|
}
|
|
128
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Override the dropdown title via `dropdownTitle`.
|
|
120
|
+
*/
|
|
129
121
|
export const CustomTitle: Story = {
|
|
130
122
|
args: {
|
|
131
123
|
...Default.args,
|