@citizenplane/pimp 14.0.2 → 14.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 +2495 -2474
- package/dist/pimp.umd.js +39 -39
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CpAccordion.vue +92 -16
- package/src/components/CpMultiselect.vue +25 -3
- package/src/stories/CpAccordion.stories.ts +1 -17
- package/src/stories/CpAccordionGroup.stories.ts +1 -1
- package/src/stories/CpMultiselect.stories.ts +3 -23
package/package.json
CHANGED
|
@@ -10,24 +10,32 @@
|
|
|
10
10
|
type="button"
|
|
11
11
|
@click="handleClick"
|
|
12
12
|
>
|
|
13
|
-
<div class="
|
|
14
|
-
<cp-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
<div class="cpAccordion__headerContent">
|
|
14
|
+
<cp-item-actions
|
|
15
|
+
v-if="hasActions"
|
|
16
|
+
:actions="resolvedActions"
|
|
17
|
+
class="cpAccordion__actions"
|
|
18
|
+
:class="actionsDynamicClasses"
|
|
19
|
+
:quick-options-limit="resolvedQuickOptionsLimit"
|
|
20
|
+
/>
|
|
21
|
+
<div class="cpAccordion__title">
|
|
22
|
+
<cp-icon v-if="displayLeadingIcon" class="cpAccordion__icon" size="16" :type="dynamicIcon" />
|
|
23
|
+
<div class="cpAccordion__leading">
|
|
24
|
+
<span class="cpAccordion__titleText">{{ title }}</span>
|
|
25
|
+
<slot name="leading-slot" />
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<div v-if="displayTrailingSection" class="cpAccordion__trailing">
|
|
29
|
+
<span v-if="hasTrailingSlot" class="cpAccordion__desktopTrailingSlot">
|
|
30
|
+
<slot name="trailing-slot" />
|
|
31
|
+
</span>
|
|
32
|
+
<cp-icon v-if="displayActionTrigger" class="cpAccordion__more" size="16" type="more-horizontal" />
|
|
33
|
+
<cp-icon v-if="displayTrailingIcon" class="cpAccordion__icon" size="16" :type="dynamicIcon" />
|
|
18
34
|
</div>
|
|
19
35
|
</div>
|
|
20
|
-
<
|
|
36
|
+
<span v-if="hasTrailingSlot" class="cpAccordion__mobileTrailingSlot">
|
|
21
37
|
<slot name="trailing-slot" />
|
|
22
|
-
|
|
23
|
-
<cp-icon v-if="displayTrailingIcon" class="cpAccordion__icon" size="16" :type="dynamicIcon" />
|
|
24
|
-
</div>
|
|
25
|
-
<cp-item-actions
|
|
26
|
-
v-if="hasActions"
|
|
27
|
-
:actions="resolvedActions"
|
|
28
|
-
class="cpAccordion__actions"
|
|
29
|
-
:quick-options-limit="resolvedQuickOptionsLimit"
|
|
30
|
-
/>
|
|
38
|
+
</span>
|
|
31
39
|
</button>
|
|
32
40
|
<cp-transition-expand>
|
|
33
41
|
<div v-if="isToggled" :id="accordionContentId" :aria-labelledby="accordionId" class="cpAccordion__content">
|
|
@@ -38,7 +46,7 @@
|
|
|
38
46
|
</template>
|
|
39
47
|
|
|
40
48
|
<script setup lang="ts">
|
|
41
|
-
import { computed, ref, useId } from 'vue'
|
|
49
|
+
import { computed, ref, useId, useSlots } from 'vue'
|
|
42
50
|
|
|
43
51
|
import type { MenuItem } from 'primevue/menuitem'
|
|
44
52
|
|
|
@@ -64,6 +72,8 @@ export type CpAccordionProps =
|
|
|
64
72
|
|
|
65
73
|
const props = defineProps<CpAccordionProps>()
|
|
66
74
|
|
|
75
|
+
const slots = useSlots()
|
|
76
|
+
|
|
67
77
|
const isToggled = ref(props.defaultOpenState)
|
|
68
78
|
|
|
69
79
|
const accordionId = useId()
|
|
@@ -87,15 +97,22 @@ const resolvedHideActionTrigger = computed(() => {
|
|
|
87
97
|
const hasActions = computed(() => resolvedActions.value.length > 0)
|
|
88
98
|
const hasLeadingIcon = computed(() => resolvedIconPosition.value === 'leading')
|
|
89
99
|
const hasTrailingIcon = computed(() => resolvedIconPosition.value === 'trailing')
|
|
100
|
+
const hasTrailingSlot = computed(() => !!slots['trailing-slot'])
|
|
90
101
|
|
|
91
102
|
const displayActionTrigger = computed(() => !resolvedHideActionTrigger.value && hasActions.value)
|
|
92
103
|
const displayLeadingIcon = computed(() => hasLeadingIcon.value || hasActions.value)
|
|
93
104
|
const displayTrailingIcon = computed(() => hasTrailingIcon.value && !hasActions.value)
|
|
94
105
|
|
|
106
|
+
const displayTrailingSection = computed(() => {
|
|
107
|
+
return hasTrailingSlot.value || displayActionTrigger.value || displayTrailingIcon.value
|
|
108
|
+
})
|
|
109
|
+
|
|
95
110
|
const dynamicIcon = computed(() => (isToggled.value ? 'chevron-up' : 'chevron-down'))
|
|
96
111
|
|
|
97
112
|
const dynamicClasses = computed(() => [{ 'cpAccordion--isOpen': isToggled.value }])
|
|
98
113
|
|
|
114
|
+
const actionsDynamicClasses = computed(() => ({ 'cpAccordion__actions--hasTrailingSlot': hasTrailingSlot.value }))
|
|
115
|
+
|
|
99
116
|
const handleClick = () => (isToggled.value = !isToggled.value)
|
|
100
117
|
</script>
|
|
101
118
|
|
|
@@ -106,6 +123,14 @@ const handleClick = () => (isToggled.value = !isToggled.value)
|
|
|
106
123
|
background-color: var(--cp-background-primary);
|
|
107
124
|
width: 100%;
|
|
108
125
|
|
|
126
|
+
&__headerContent {
|
|
127
|
+
display: flex;
|
|
128
|
+
flex-shrink: 0;
|
|
129
|
+
width: 100%;
|
|
130
|
+
justify-content: space-between;
|
|
131
|
+
gap: var(--cp-spacing-lg);
|
|
132
|
+
}
|
|
133
|
+
|
|
109
134
|
&__header {
|
|
110
135
|
@extend %u-focus-outline;
|
|
111
136
|
|
|
@@ -129,6 +154,7 @@ const handleClick = () => (isToggled.value = !isToggled.value)
|
|
|
129
154
|
font-weight: 500;
|
|
130
155
|
font-size: var(--cp-text-size-sm);
|
|
131
156
|
line-height: var(--cp-line-height-sm);
|
|
157
|
+
overflow: hidden;
|
|
132
158
|
}
|
|
133
159
|
|
|
134
160
|
&__icon {
|
|
@@ -140,6 +166,12 @@ const handleClick = () => (isToggled.value = !isToggled.value)
|
|
|
140
166
|
&__leading {
|
|
141
167
|
display: flex;
|
|
142
168
|
flex-direction: column;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
text-align: left;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
&__titleText {
|
|
174
|
+
@extend %u-text-ellipsis;
|
|
143
175
|
}
|
|
144
176
|
|
|
145
177
|
&__trailing {
|
|
@@ -149,15 +181,59 @@ const handleClick = () => (isToggled.value = !isToggled.value)
|
|
|
149
181
|
position: relative;
|
|
150
182
|
}
|
|
151
183
|
|
|
184
|
+
&__desktopTrailingSlot {
|
|
185
|
+
display: flex;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
&__mobileTrailingSlot {
|
|
189
|
+
display: none;
|
|
190
|
+
text-align: left;
|
|
191
|
+
padding-left: var(--cp-spacing-3xl);
|
|
192
|
+
}
|
|
193
|
+
|
|
152
194
|
&__actions {
|
|
195
|
+
right: 0;
|
|
196
|
+
top: 0;
|
|
197
|
+
height: 100%;
|
|
153
198
|
background: linear-gradient(270deg, var(--cp-utility-neutral-100) 0%, rgba(242, 246, 250, 0) 100%);
|
|
154
199
|
padding: 0 var(--cp-spacing-md) 0 15%;
|
|
200
|
+
transform: translate3d(calc(var(--cp-dimensions-1) * 1.25), 0%, 0);
|
|
155
201
|
display: flex;
|
|
156
202
|
align-items: center;
|
|
203
|
+
z-index: 1;
|
|
157
204
|
}
|
|
158
205
|
|
|
159
206
|
&__more {
|
|
160
207
|
color: var(--cp-foreground-primary);
|
|
161
208
|
}
|
|
162
209
|
}
|
|
210
|
+
|
|
211
|
+
@media (hover: hover) and (pointer: fine) {
|
|
212
|
+
[cp-item-actions-trigger]:has(.cpItemActions--isDropdownOpen) .cpItemActions,
|
|
213
|
+
[cp-item-actions-trigger]:is(:focus-within, :focus-visible, :hover) .cpItemActions {
|
|
214
|
+
transform: translate3d(0, 0, 0);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// var(--cp-breakpoint-sm) = 40rem = 400px
|
|
219
|
+
@include mx.media-query-max(40rem) {
|
|
220
|
+
.cpAccordion {
|
|
221
|
+
&__header {
|
|
222
|
+
flex-wrap: wrap;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
&__desktopTrailingSlot {
|
|
226
|
+
display: none;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
&__mobileTrailingSlot {
|
|
230
|
+
display: flex;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
&__actions--hasTrailingSlot {
|
|
234
|
+
align-items: flex-start;
|
|
235
|
+
top: fn.px-to-rem(8);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
163
239
|
</style>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
:option-label
|
|
29
29
|
:placeholder
|
|
30
30
|
:pt="passThroughConfig"
|
|
31
|
-
:suggestions="
|
|
31
|
+
:suggestions="dynamicOptions"
|
|
32
32
|
:typeahead
|
|
33
33
|
@click="handleClick"
|
|
34
34
|
@complete="handleSearch"
|
|
@@ -126,6 +126,7 @@ interface Props {
|
|
|
126
126
|
modelValue?: Record<string, unknown>[] | Record<string, unknown> | string[] | null
|
|
127
127
|
multiple?: boolean
|
|
128
128
|
name?: string
|
|
129
|
+
onSearch?: (query: string) => void
|
|
129
130
|
optionDisabled?: string | ((option: unknown) => boolean)
|
|
130
131
|
optionLabel?: string
|
|
131
132
|
options?: unknown[]
|
|
@@ -141,7 +142,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
141
142
|
label: '',
|
|
142
143
|
name: '',
|
|
143
144
|
placeholder: '',
|
|
144
|
-
optionLabel:
|
|
145
|
+
optionLabel: 'name',
|
|
145
146
|
trackBy: undefined,
|
|
146
147
|
emptyMessage: 'No results found',
|
|
147
148
|
errorMessage: '',
|
|
@@ -149,6 +150,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
149
150
|
optionDisabled: 'disabled',
|
|
150
151
|
options: () => [],
|
|
151
152
|
size: 'md',
|
|
153
|
+
onSearch: undefined,
|
|
152
154
|
})
|
|
153
155
|
|
|
154
156
|
const emit = defineEmits<Emits>()
|
|
@@ -218,7 +220,27 @@ const displayClearButton = computed(() => {
|
|
|
218
220
|
return props.isClearable && !isEmpty(selectModel.value)
|
|
219
221
|
})
|
|
220
222
|
|
|
221
|
-
const
|
|
223
|
+
const hasRegisteredOnSearch = computed(() => props.onSearch !== undefined)
|
|
224
|
+
|
|
225
|
+
const handleSearch = (event: { query: string }) => {
|
|
226
|
+
if (hasRegisteredOnSearch.value) {
|
|
227
|
+
return emit('search', event.query)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// If no onSearch is registered, toggle the dropdown (if hidden) to show the options
|
|
231
|
+
if (!isDropdownOpen.value) {
|
|
232
|
+
toggleDropdown()
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const dynamicOptions = computed(() => {
|
|
237
|
+
if (hasRegisteredOnSearch.value) return props.options
|
|
238
|
+
|
|
239
|
+
return props.options.filter((option: unknown) => {
|
|
240
|
+
const optionLabel = typeof option === 'object' ? (option as Record<string, unknown>)[props.optionLabel] : option
|
|
241
|
+
return (optionLabel as string).toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
242
|
+
})
|
|
243
|
+
})
|
|
222
244
|
const handleClear = () => (selectModel.value = null)
|
|
223
245
|
const handleOverlayShown = () => emit('overlayShown')
|
|
224
246
|
const handleOverlayHidden = () => emit('overlayHidden')
|
|
@@ -37,7 +37,7 @@ const defaultStoryActions: MenuItem[] = [
|
|
|
37
37
|
]
|
|
38
38
|
|
|
39
39
|
const meta = {
|
|
40
|
-
title: '
|
|
40
|
+
title: 'Visual/CpAccordion',
|
|
41
41
|
component: CpAccordion,
|
|
42
42
|
argTypes: {
|
|
43
43
|
title: {
|
|
@@ -177,22 +177,6 @@ export const Documentation: Story = {
|
|
|
177
177
|
<div :style="sampleContentStyle" />
|
|
178
178
|
</CpAccordion>
|
|
179
179
|
</div>
|
|
180
|
-
<div :style="docCellStyle">
|
|
181
|
-
<span :style="docLabelStyle">Hover</span>
|
|
182
|
-
<CpAccordion
|
|
183
|
-
title="DIDA TRAVEL"
|
|
184
|
-
:default-open-state="false"
|
|
185
|
-
icon-position="leading"
|
|
186
|
-
style="background: var(--cp-background-primary-hover);"
|
|
187
|
-
:actions="actions"
|
|
188
|
-
:quick-options-limit="3"
|
|
189
|
-
>
|
|
190
|
-
<template #trailing-slot>
|
|
191
|
-
This is a trailing slot
|
|
192
|
-
</template>
|
|
193
|
-
<div :style="sampleContentStyle" />
|
|
194
|
-
</CpAccordion>
|
|
195
|
-
</div>
|
|
196
180
|
<div :style="docCellStyle">
|
|
197
181
|
<span :style="docLabelStyle">Hide Action Trigger</span>
|
|
198
182
|
<CpAccordion
|
|
@@ -113,43 +113,23 @@ const meta = {
|
|
|
113
113
|
export default meta
|
|
114
114
|
type Story = StoryObj<typeof meta>
|
|
115
115
|
|
|
116
|
-
export const
|
|
116
|
+
export const DefaultSearchHandler: Story = {
|
|
117
117
|
args: {
|
|
118
118
|
placeholder: 'Select a supplier',
|
|
119
119
|
multiple: false,
|
|
120
120
|
options: supplierOptions,
|
|
121
|
-
optionLabel: 'name',
|
|
122
121
|
trackBy: 'id',
|
|
123
122
|
isClearable: true,
|
|
124
123
|
},
|
|
125
124
|
render: (args) => ({
|
|
126
125
|
components: { CpMultiselect },
|
|
127
126
|
setup() {
|
|
128
|
-
const searchQuery = ref('')
|
|
129
|
-
const isLoading = ref(false)
|
|
130
|
-
|
|
131
|
-
const originalOptions = ref(args.options)
|
|
132
|
-
const dynamicOptions = ref(toValue(originalOptions))
|
|
133
127
|
const selectedSupplier = ref(null)
|
|
134
|
-
|
|
135
|
-
const handleSearch = async (query: string) => {
|
|
136
|
-
isLoading.value = true
|
|
137
|
-
searchQuery.value = query
|
|
138
|
-
|
|
139
|
-
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
140
|
-
|
|
141
|
-
dynamicOptions.value = originalOptions.value?.filter((option) => {
|
|
142
|
-
return (option as IOption).name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
isLoading.value = false
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { args, selectedSupplier, dynamicOptions, handleSearch, isLoading }
|
|
128
|
+
return { args, selectedSupplier }
|
|
149
129
|
},
|
|
150
130
|
template: `
|
|
151
131
|
<div style="padding: 20px;width: 25rem;">
|
|
152
|
-
<CpMultiselect v-model="selectedSupplier" v-bind="args"
|
|
132
|
+
<CpMultiselect v-model="selectedSupplier" v-bind="args">
|
|
153
133
|
<template #prefix>
|
|
154
134
|
<cp-partner-badge type="supplier" size="sm" />
|
|
155
135
|
</template>
|