@citizenplane/pimp 10.9.2 → 11.0.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 +2317 -2284
- package/dist/pimp.umd.js +43 -43
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/assets/styles/utilities/_index.scss +9 -0
- package/src/components/CpDialog.vue +79 -37
- package/src/components/CpMultiselect.vue +48 -21
- package/src/stories/CpDialog.stories.ts +72 -15
package/package.json
CHANGED
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
overflow: hidden;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
.u-layout-box-padding,
|
|
11
|
+
%u-layout-box-padding {
|
|
12
|
+
padding: var(--cp-spacing-2xl);
|
|
13
|
+
|
|
14
|
+
@media (max-width: 650px) {
|
|
15
|
+
padding: var(--cp-spacing-xl);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
.u-asterisk {
|
|
11
20
|
position: relative;
|
|
12
21
|
top: fn.px-to-rem(-3);
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cpDialog">
|
|
3
|
-
<dialog
|
|
4
|
-
|
|
3
|
+
<dialog
|
|
4
|
+
ref="dialogElement"
|
|
5
|
+
:aria-describedby="ariaDescribedby"
|
|
6
|
+
:aria-labelledby="titleId"
|
|
7
|
+
aria-modal="true"
|
|
8
|
+
class="cpDialog__dialog"
|
|
9
|
+
@keydown.esc.stop.prevent="handleClose"
|
|
10
|
+
>
|
|
11
|
+
<div aria-hidden="true" class="cpDialog__overlay" />
|
|
5
12
|
<main ref="dialogContainer" class="cpDialog__container" :style="dynamicStyle" @keydown.tab="trapFocus">
|
|
6
|
-
<header
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
<header class="cpDialog__header">
|
|
14
|
+
<div class="cpDialog__left">
|
|
15
|
+
<div class="cpDialog__title">
|
|
16
|
+
<slot name="title">
|
|
17
|
+
<h2 :id="titleId">{{ title }}</h2>
|
|
18
|
+
</slot>
|
|
19
|
+
</div>
|
|
20
|
+
<div v-if="hasSubtitle" :id="subtitleId" class="cpDialog__subtitle">
|
|
21
|
+
<slot name="subtitle">
|
|
22
|
+
<p>{{ subtitle }}</p>
|
|
23
|
+
</slot>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<button aria-label="Close dialog" class="cpDialog__close" type="button" @click="handleClose">
|
|
27
|
+
<cp-icon aria-hidden="true" type="x" />
|
|
10
28
|
</button>
|
|
11
29
|
</header>
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
</section>
|
|
16
|
-
</slot>
|
|
30
|
+
<section class="cpDialog__content">
|
|
31
|
+
<slot />
|
|
32
|
+
</section>
|
|
17
33
|
<footer v-if="hasFooterSlot" class="cpDialog__footer">
|
|
18
34
|
<slot name="footer" />
|
|
19
35
|
</footer>
|
|
@@ -23,12 +39,14 @@
|
|
|
23
39
|
</template>
|
|
24
40
|
|
|
25
41
|
<script setup lang="ts">
|
|
26
|
-
import { computed, ref, useSlots, onMounted, nextTick, onBeforeUnmount } from 'vue'
|
|
42
|
+
import { computed, ref, useSlots, onMounted, nextTick, onBeforeUnmount, useId } from 'vue'
|
|
27
43
|
|
|
28
44
|
import { getKeyboardFocusableElements, handleTrapFocus } from '@/helpers/dom'
|
|
29
45
|
|
|
30
46
|
interface Props {
|
|
31
47
|
maxWidth?: number
|
|
48
|
+
subtitle?: string
|
|
49
|
+
title: string
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
interface Emits {
|
|
@@ -37,10 +55,17 @@ interface Emits {
|
|
|
37
55
|
|
|
38
56
|
const props = withDefaults(defineProps<Props>(), {
|
|
39
57
|
maxWidth: 600,
|
|
58
|
+
subtitle: '',
|
|
40
59
|
})
|
|
41
60
|
|
|
42
61
|
const emit = defineEmits<Emits>()
|
|
43
62
|
|
|
63
|
+
const dialogId = useId()
|
|
64
|
+
const titleId = computed(() => `${dialogId}-title`)
|
|
65
|
+
const subtitleId = computed(() => `${dialogId}-subtitle`)
|
|
66
|
+
|
|
67
|
+
const ariaDescribedby = computed(() => (hasSubtitle.value ? subtitleId.value : undefined))
|
|
68
|
+
|
|
44
69
|
const slots = useSlots()
|
|
45
70
|
|
|
46
71
|
const dialogElement = ref<HTMLDialogElement | null>(null)
|
|
@@ -48,8 +73,8 @@ const dialogContainer = ref<HTMLElement | null>(null)
|
|
|
48
73
|
|
|
49
74
|
const dynamicStyle = computed(() => ({ maxWidth: `${props.maxWidth}px` }))
|
|
50
75
|
|
|
51
|
-
const
|
|
52
|
-
|
|
76
|
+
const hasSubtitleSlot = computed(() => !!slots.subtitle)
|
|
77
|
+
const hasSubtitle = computed(() => !!props.subtitle || hasSubtitleSlot.value)
|
|
53
78
|
const hasFooterSlot = computed(() => !!slots.footer)
|
|
54
79
|
|
|
55
80
|
const handleClose = () => emit('close')
|
|
@@ -151,64 +176,81 @@ $dialog-breakpoint: 550px;
|
|
|
151
176
|
|
|
152
177
|
&__header,
|
|
153
178
|
&__footer {
|
|
154
|
-
|
|
155
|
-
align-items: center;
|
|
156
|
-
justify-content: space-between;
|
|
179
|
+
padding: var(--cp-spacing-xl) var(--cp-spacing-2xl);
|
|
157
180
|
}
|
|
158
181
|
|
|
159
182
|
&__header {
|
|
160
|
-
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: flex-start;
|
|
185
|
+
justify-content: space-between;
|
|
186
|
+
gap: var(--cp-spacing-md);
|
|
161
187
|
border-bottom: 1px solid var(--cp-border-soft);
|
|
162
188
|
}
|
|
163
189
|
|
|
190
|
+
&__left {
|
|
191
|
+
display: flex;
|
|
192
|
+
flex-direction: column;
|
|
193
|
+
min-width: 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
&__title > * {
|
|
197
|
+
@extend %u-text-ellipsis;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
&__title,
|
|
201
|
+
&__title > h2 {
|
|
202
|
+
font-size: var(--cp-text-size-xl);
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
line-height: var(--cp-line-height-xl);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
&__subtitle,
|
|
208
|
+
&__subtitle > p {
|
|
209
|
+
font-size: var(--cp-text-size-sm);
|
|
210
|
+
font-weight: 400;
|
|
211
|
+
line-height: var(--cp-line-height-md);
|
|
212
|
+
color: var(--cp-text-secondary);
|
|
213
|
+
}
|
|
214
|
+
|
|
164
215
|
&__close {
|
|
165
|
-
|
|
166
|
-
top: var(--cp-spacing-2xl);
|
|
167
|
-
right: var(--cp-spacing-2xl);
|
|
168
|
-
display: inline-flex;
|
|
216
|
+
display: flex;
|
|
169
217
|
align-items: center;
|
|
170
218
|
justify-content: center;
|
|
171
219
|
padding: var(--cp-spacing-sm);
|
|
172
220
|
border-radius: var(--cp-radius-md);
|
|
173
221
|
color: var(--cp-foreground-secondary);
|
|
174
|
-
transition:
|
|
175
|
-
transform: translate(var(--cp-dimensions-1), calc(var(--cp-dimensions-1) * -1));
|
|
222
|
+
transition: 200ms ease-in-out;
|
|
176
223
|
transition-property: color, background-color;
|
|
177
224
|
|
|
178
225
|
&:hover {
|
|
179
226
|
color: var(--cp-foreground-primary);
|
|
227
|
+
background-color: var(--cp-background-secondary);
|
|
180
228
|
}
|
|
181
229
|
|
|
182
230
|
&:focus-visible {
|
|
183
|
-
outline:
|
|
231
|
+
outline: fn.px-to-rem(2) solid var(--cp-focus);
|
|
184
232
|
}
|
|
185
233
|
}
|
|
186
234
|
|
|
187
235
|
&__content {
|
|
236
|
+
@extend %u-layout-box-padding;
|
|
237
|
+
|
|
188
238
|
overflow: auto;
|
|
189
239
|
min-height: 0;
|
|
190
240
|
flex: 1;
|
|
191
241
|
}
|
|
192
242
|
|
|
193
243
|
&__footer {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: space-between;
|
|
247
|
+
|
|
194
248
|
&:not(:empty) {
|
|
195
249
|
border-top: 1px solid var(--cp-border-soft);
|
|
196
|
-
padding: var(--cp-spacing-xl) var(--cp-spacing-2xl);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
&--noBorder {
|
|
200
|
-
border-top: none;
|
|
201
250
|
}
|
|
202
251
|
}
|
|
203
252
|
}
|
|
204
253
|
|
|
205
|
-
@media screen and (max-width: $dialog-breakpoint) {
|
|
206
|
-
.cpDialog__close {
|
|
207
|
-
top: var(--cp-spacing-xl);
|
|
208
|
-
right: var(--cp-spacing-xl);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
254
|
@media (max-width: 650px) {
|
|
213
255
|
.cpDialog__footer {
|
|
214
256
|
padding: var(--cp-spacing-xl);
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cpMultiselect">
|
|
3
|
-
<base-input-label
|
|
3
|
+
<base-input-label
|
|
4
|
+
v-if="label"
|
|
5
|
+
:id="labelId"
|
|
6
|
+
class="cpMultiselect__label"
|
|
7
|
+
:for="multiselectId"
|
|
8
|
+
:is-invalid
|
|
9
|
+
:required
|
|
10
|
+
>
|
|
4
11
|
{{ label }}
|
|
5
12
|
</base-input-label>
|
|
6
13
|
<auto-complete
|
|
7
14
|
ref="multiselect"
|
|
8
15
|
v-model="selectModel"
|
|
9
16
|
:append-to
|
|
17
|
+
:aria-labelledby="ariaLabelledby"
|
|
10
18
|
auto-option-focus
|
|
11
19
|
:data-key="trackBy"
|
|
12
20
|
:disabled
|
|
13
21
|
:force-selection
|
|
14
22
|
:input-class
|
|
23
|
+
:input-id="multiselectId"
|
|
15
24
|
:invalid="isInvalid"
|
|
16
25
|
:multiple
|
|
17
26
|
:name
|
|
@@ -24,6 +33,8 @@
|
|
|
24
33
|
@click="handleClick"
|
|
25
34
|
@complete="handleSearch"
|
|
26
35
|
@hide="handleOverlayHidden"
|
|
36
|
+
@keydown.arrow-down="handleArrowKeys"
|
|
37
|
+
@keydown.arrow-up="handleArrowKeys"
|
|
27
38
|
@keydown.enter="toggleDropdown"
|
|
28
39
|
@keydown.esc.stop
|
|
29
40
|
@show="handleOverlayShown"
|
|
@@ -61,12 +72,18 @@
|
|
|
61
72
|
v-else
|
|
62
73
|
class="cpMultiselect__toggle"
|
|
63
74
|
:disabled="disabled"
|
|
75
|
+
tabindex="-1"
|
|
64
76
|
type="button"
|
|
65
77
|
@click.stop="handleDropdownIconClick"
|
|
66
78
|
>
|
|
67
79
|
<cp-icon class="cpMultiselect__dropdownIcon" :class="chevronDynamicClass" type="chevron-down" />
|
|
68
80
|
</button>
|
|
69
|
-
<base-select-clear-button
|
|
81
|
+
<base-select-clear-button
|
|
82
|
+
v-if="displayClearButton"
|
|
83
|
+
class="cpMultiselect__clear"
|
|
84
|
+
tabindex="-1"
|
|
85
|
+
@click="handleClear"
|
|
86
|
+
/>
|
|
70
87
|
</template>
|
|
71
88
|
</auto-complete>
|
|
72
89
|
<transition-expand mode="out-in">
|
|
@@ -80,7 +97,7 @@
|
|
|
80
97
|
<script setup lang="ts">
|
|
81
98
|
import { absolutePosition, getOuterWidth } from '@primeuix/utils/dom'
|
|
82
99
|
import AutoComplete from 'primevue/autocomplete'
|
|
83
|
-
import { ref, computed, onMounted, useSlots } from 'vue'
|
|
100
|
+
import { ref, computed, onMounted, useSlots, useId } from 'vue'
|
|
84
101
|
|
|
85
102
|
import BaseInputLabel from '@/components/BaseInputLabel.vue'
|
|
86
103
|
import BaseSelectClearButton from '@/components/BaseSelectClearButton.vue'
|
|
@@ -138,14 +155,20 @@ const emit = defineEmits<Emits>()
|
|
|
138
155
|
|
|
139
156
|
const slots = useSlots()
|
|
140
157
|
|
|
158
|
+
const multiselectId = useId()
|
|
159
|
+
const labelId = useId()
|
|
160
|
+
|
|
161
|
+
const ariaLabelledby = computed(() => (props.label ? labelId : undefined))
|
|
162
|
+
|
|
141
163
|
const selectModel = computed({
|
|
142
164
|
get() {
|
|
143
165
|
return props.modelValue
|
|
144
166
|
},
|
|
145
|
-
set(value) {
|
|
167
|
+
set(value: string | object | string[] | null) {
|
|
146
168
|
if (typeof value === 'string') {
|
|
147
169
|
return
|
|
148
170
|
}
|
|
171
|
+
|
|
149
172
|
emit('update:modelValue', value)
|
|
150
173
|
},
|
|
151
174
|
})
|
|
@@ -167,13 +190,21 @@ const passThroughConfig = computed(() => ({
|
|
|
167
190
|
loader: { class: 'cpMultiselect__hidden' },
|
|
168
191
|
}))
|
|
169
192
|
|
|
170
|
-
|
|
193
|
+
type ExtendedAutoComplete = InstanceType<typeof AutoComplete> & {
|
|
194
|
+
$el: HTMLElement
|
|
195
|
+
alignOverlay: () => void
|
|
196
|
+
hide: () => void
|
|
197
|
+
overlay: HTMLElement
|
|
198
|
+
overlayVisible: boolean
|
|
199
|
+
show: () => void
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const multiselect = ref<ExtendedAutoComplete | null>(null)
|
|
171
203
|
|
|
172
204
|
const searchQuery = ref('')
|
|
173
205
|
|
|
174
206
|
const typeahead = computed(() => !props.withoutTypeahead)
|
|
175
207
|
|
|
176
|
-
// @ts-expect-error 'overlayVisible' does not exist on type instance of AutoComplete
|
|
177
208
|
const isDropdownOpen = computed(() => multiselect.value?.overlayVisible)
|
|
178
209
|
|
|
179
210
|
const chevronDynamicClass = computed(() => {
|
|
@@ -202,11 +233,10 @@ const handleValueChange = (newValue: string | object) => {
|
|
|
202
233
|
|
|
203
234
|
const getInputElement = () => {
|
|
204
235
|
if (!multiselect.value) return null
|
|
205
|
-
// @ts-expect-error '$el' does not exist on type instance of AutoComplete
|
|
206
236
|
return multiselect.value.$el?.querySelector('input') || null
|
|
207
237
|
}
|
|
208
238
|
|
|
209
|
-
const
|
|
239
|
+
const selectInputElement = () => {
|
|
210
240
|
const inputElement = getInputElement()
|
|
211
241
|
if (inputElement) inputElement.select()
|
|
212
242
|
}
|
|
@@ -215,13 +245,13 @@ const focusAndSelectInput = () => {
|
|
|
215
245
|
const inputElement = getInputElement()
|
|
216
246
|
if (inputElement) {
|
|
217
247
|
inputElement.focus()
|
|
218
|
-
|
|
248
|
+
selectInputElement()
|
|
219
249
|
}
|
|
220
250
|
}
|
|
221
251
|
|
|
222
252
|
const handleClick = () => {
|
|
223
253
|
toggleDropdown()
|
|
224
|
-
|
|
254
|
+
selectInputElement()
|
|
225
255
|
}
|
|
226
256
|
|
|
227
257
|
const handleDropdownIconClick = () => {
|
|
@@ -229,12 +259,15 @@ const handleDropdownIconClick = () => {
|
|
|
229
259
|
focusAndSelectInput()
|
|
230
260
|
}
|
|
231
261
|
|
|
262
|
+
const handleArrowKeys = () => {
|
|
263
|
+
if (isDropdownOpen.value) return
|
|
264
|
+
return toggleDropdown()
|
|
265
|
+
}
|
|
266
|
+
|
|
232
267
|
const toggleDropdown = () => {
|
|
233
268
|
if (isDropdownOpen.value) {
|
|
234
|
-
// @ts-expect-error 'hide' does not exist on type instance of AutoComplete
|
|
235
269
|
multiselect.value?.hide()
|
|
236
270
|
} else {
|
|
237
|
-
// @ts-expect-error 'show' does not exist on type instance of AutoComplete
|
|
238
271
|
multiselect.value?.show()
|
|
239
272
|
}
|
|
240
273
|
}
|
|
@@ -245,23 +278,17 @@ const handleUpdateModelValue = (value: Record<string, unknown> | string[] | null
|
|
|
245
278
|
}
|
|
246
279
|
|
|
247
280
|
const overrideAlignOverlay = () => {
|
|
248
|
-
if (multiselect.value)
|
|
249
|
-
|
|
250
|
-
multiselect.value.alignOverlay = alignOverlay
|
|
251
|
-
}
|
|
281
|
+
if (!multiselect.value) return
|
|
282
|
+
multiselect.value.alignOverlay = alignOverlay
|
|
252
283
|
}
|
|
253
284
|
|
|
254
285
|
const alignOverlay = () => {
|
|
255
|
-
// @ts-expect-error 'el' does not exist on type instance of AutoComplete
|
|
256
286
|
const target = multiselect.value?.$el
|
|
257
287
|
|
|
258
|
-
// @ts-expect-error 'overlay' does not exist on type instance of AutoComplete
|
|
259
288
|
if (!multiselect.value?.overlay || !target) return
|
|
260
289
|
|
|
261
|
-
// @ts-expect-error 'overlay' does not exist on type instance of AutoComplete
|
|
262
290
|
multiselect.value.overlay.style.width = `${getOuterWidth(target)}px`
|
|
263
291
|
|
|
264
|
-
// @ts-expect-error 'overlay' does not exist on type instance of AutoComplete
|
|
265
292
|
absolutePosition(multiselect.value.overlay, target)
|
|
266
293
|
}
|
|
267
294
|
|
|
@@ -470,7 +497,7 @@ onMounted(() => overrideAlignOverlay())
|
|
|
470
497
|
color: var(--cp-foreground-primary);
|
|
471
498
|
}
|
|
472
499
|
|
|
473
|
-
&[data-p-selected='true'] {
|
|
500
|
+
&[data-p-selected='true']:not([data-p-focused='true']) {
|
|
474
501
|
background: var(--cp-background-accent-primary);
|
|
475
502
|
}
|
|
476
503
|
|
|
@@ -14,13 +14,6 @@ const meta = {
|
|
|
14
14
|
},
|
|
15
15
|
onClose: { action: 'closed' },
|
|
16
16
|
},
|
|
17
|
-
parameters: {
|
|
18
|
-
styles: {
|
|
19
|
-
'.header': {
|
|
20
|
-
color: 'red',
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
17
|
} satisfies Meta<typeof CpDialog>
|
|
25
18
|
|
|
26
19
|
export default meta
|
|
@@ -29,6 +22,8 @@ type Story = StoryObj<typeof meta>
|
|
|
29
22
|
export const Default: Story = {
|
|
30
23
|
args: {
|
|
31
24
|
maxWidth: 600,
|
|
25
|
+
title: 'Dialog title',
|
|
26
|
+
subtitle: 'Dialog subtitle',
|
|
32
27
|
},
|
|
33
28
|
render: (args) => ({
|
|
34
29
|
setup() {
|
|
@@ -39,14 +34,76 @@ export const Default: Story = {
|
|
|
39
34
|
<CpButton @click="isOpen = true">Open Dialog</CpButton>
|
|
40
35
|
<CpTransitionDialog>
|
|
41
36
|
<CpDialog v-bind="args" v-if="isOpen" @close="isOpen = false">
|
|
42
|
-
<template #
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
<template #title>Header slot</template>
|
|
38
|
+
<template #subtitle>Subtitle</template>
|
|
39
|
+
<p>This is the default slot content. You can put any content here.</p>
|
|
40
|
+
<template #footer>
|
|
41
|
+
<CpButton @click="isOpen = false">Cancel</CpButton>
|
|
42
|
+
This is the footer slot
|
|
43
|
+
</template>
|
|
44
|
+
</CpDialog>
|
|
45
|
+
</CpTransitionDialog>
|
|
46
|
+
`,
|
|
47
|
+
}),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const TitleSubtitleWithProps: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
maxWidth: 600,
|
|
53
|
+
title: 'Dialog title',
|
|
54
|
+
},
|
|
55
|
+
render: (args) => ({
|
|
56
|
+
setup() {
|
|
57
|
+
const isOpen = ref(false)
|
|
58
|
+
return { args, isOpen }
|
|
59
|
+
},
|
|
60
|
+
template: `
|
|
61
|
+
<CpButton @click="isOpen = true">Open Dialog with string title/subtitle</CpButton>
|
|
62
|
+
<CpTransitionDialog>
|
|
63
|
+
<CpDialog v-bind="args" v-if="isOpen" @close="isOpen = false">
|
|
64
|
+
<p>This is the default slot content. You can put any content here.</p>
|
|
65
|
+
<template #footer>
|
|
66
|
+
<CpButton @click="isOpen = false">Cancel</CpButton>
|
|
67
|
+
This is the footer slot
|
|
68
|
+
</template>
|
|
69
|
+
</CpDialog>
|
|
70
|
+
</CpTransitionDialog>
|
|
71
|
+
`,
|
|
72
|
+
}),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const TitleSubtitleWithSlots: Story = {
|
|
76
|
+
args: {
|
|
77
|
+
maxWidth: 560,
|
|
78
|
+
titleTag: 'div',
|
|
79
|
+
subtitleTag: 'div',
|
|
80
|
+
},
|
|
81
|
+
render: (args) => ({
|
|
82
|
+
setup() {
|
|
83
|
+
const isOpen = ref(false)
|
|
84
|
+
return { args, isOpen }
|
|
85
|
+
},
|
|
86
|
+
template: `
|
|
87
|
+
<CpButton @click="isOpen = true">Open Dialog (flex title/subtitle)</CpButton>
|
|
88
|
+
<CpTransitionDialog>
|
|
89
|
+
<CpDialog v-bind="args" v-if="isOpen" @close="isOpen = false" title-tag="div" subtitle-tag="div">
|
|
90
|
+
<template #title>
|
|
91
|
+
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
|
|
92
|
+
<cp-icon type="info" style="flex-shrink: 0;" />
|
|
93
|
+
<span>Dialog with flex layout</span>
|
|
94
|
+
</div>
|
|
95
|
+
</template>
|
|
96
|
+
<template #subtitle>
|
|
97
|
+
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
|
|
98
|
+
<span>Optional info</span>
|
|
99
|
+
<span style="color: var(--cp-text-tertiary);">•</span>
|
|
100
|
+
<span>Last updated today</span>
|
|
101
|
+
</div>
|
|
102
|
+
</template>
|
|
103
|
+
<p>Body content. Title and subtitle above are divs with flex layout.</p>
|
|
104
|
+
<template #footer>
|
|
105
|
+
<CpButton @click="isOpen = false">Close</CpButton>
|
|
106
|
+
</template>
|
|
50
107
|
</CpDialog>
|
|
51
108
|
</CpTransitionDialog>
|
|
52
109
|
`,
|