@dumiflex/wp-kit 0.4.0 → 0.6.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/CHANGELOG.md +47 -0
- package/README.md +5 -0
- package/package.json +4 -3
- package/src/Avatar.vue +1 -1
- package/src/Banner.vue +4 -4
- package/src/Button.vue +73 -44
- package/src/Checkbox.vue +201 -63
- package/src/Chip.vue +5 -2
- package/src/CodeBlock.vue +33 -6
- package/src/ColorField.vue +60 -6
- package/src/Combobox.vue +10 -14
- package/src/ConfirmModal.vue +12 -22
- package/src/ContextMenu.vue +11 -30
- package/src/CopyButton.vue +1 -1
- package/src/DataTable.vue +3 -4
- package/src/DatePicker.vue +1 -1
- package/src/Disclosure.vue +2 -1
- package/src/Drawer.vue +2 -2
- package/src/DropZone.vue +2 -2
- package/src/HsvPicker.vue +2 -2
- package/src/IconPicker.vue +1 -1
- package/src/ImageGrid.vue +17 -15
- package/src/Lightbox.vue +6 -3
- package/src/LoadingMark.vue +93 -51
- package/src/Modal.vue +109 -44
- package/src/ModalShell.vue +26 -5
- package/src/Pagination.vue +4 -4
- package/src/Popover.vue +68 -12
- package/src/RadioGroup.vue +5 -4
- package/src/SearchInput.vue +1 -1
- package/src/Select.vue +53 -158
- package/src/Skeleton.vue +21 -5
- package/src/Slider.vue +166 -55
- package/src/SortableList.vue +1 -1
- package/src/TagInput.vue +1 -1
- package/src/Timeline.vue +2 -2
- package/src/WorkingLine.vue +320 -0
- package/src/WpIconButton.vue +3 -2
- package/src/WpImageZoom.vue +7 -7
- package/src/WpSegmented.vue +3 -1
- package/src/WpSwitch.vue +1 -1
- package/src/WpTabs.vue +1 -1
- package/src/_modal-motion.css +1 -1
- package/src/button.css +8 -6
- package/src/floating.ts +5 -0
- package/src/highlight.ts +199 -0
- package/src/hsv.ts +1 -1
- package/src/loading-mark-motion.ts +249 -0
- package/src/menu.css +51 -0
- package/src/motion.css +73 -0
- package/src/slider-math.ts +45 -1
- package/src/theme/looks.css +6 -19
- package/src/theme/tokens.css +12 -1108
- package/src/tokens.ts +8 -3
- package/src/tooltip.css +8 -5
- package/src/useDialogFocus.ts +11 -2
- package/src/CloseButton.vue +0 -69
- package/src/ColorPicker.vue +0 -201
- package/src/ConfirmDialog.vue +0 -165
- package/src/RangeSlider.vue +0 -183
- package/src/Toggle.vue +0 -36
- package/src/WpButton.vue +0 -75
- package/src/WpCheck.vue +0 -104
- package/src/_skeleton.css +0 -50
package/src/tokens.ts
CHANGED
|
@@ -45,12 +45,17 @@ export const SHARED_TOKENS = [
|
|
|
45
45
|
export const OPTIONAL_TOKENS = ["--wp-bg-deep", "--wp-bg2"] as const;
|
|
46
46
|
|
|
47
47
|
/** Set by the piece itself, on its own root, not by the host. */
|
|
48
|
-
export const LOCAL_TOKENS = ["--wp-hsv-hue", "--wp-lm-
|
|
48
|
+
export const LOCAL_TOKENS = ["--wp-hsv-hue", "--wp-lm-o", "--wp-select-line-h", "--wp-list-row-depth", "--wp-list-row-icon", "--wp-chip-c", "--wp-field-h",
|
|
49
|
+
// WorkingLine's marks and word (registered properties it animates).
|
|
50
|
+
"--wp-wl-t", "--wp-wl-k", "--wp-wl-x", "--wp-wl-f", "--wp-wl-r", "--wp-wl-s", "--wp-wl-o", "--wp-wl-wo", "--wp-wl-wb",
|
|
51
|
+
"--wp-wl-dx", "--wp-wl-dy", "--wp-wl-big", "--wp-wl-a", "--wp-wl-b",
|
|
52
|
+
// motion.css entrances.
|
|
53
|
+
"--wp-enter-o", "--wp-enter-s", "--wp-enter-y", "--wp-enter-x"] as const;
|
|
49
54
|
|
|
50
55
|
/** Pieces that need a page with src/theme/ loaded (not the canvas). */
|
|
51
56
|
export const PAGE_PIECES = [
|
|
52
57
|
"axis-color.ts", "kind-colour.ts", "matrix-axis.ts", "BundleMeta.vue", "ConstraintMatrixPreview.vue", "PostCard.vue", "TokenizedValue.vue",
|
|
53
|
-
"
|
|
58
|
+
"Card.vue", "Chip.vue", "ChoiceCards.vue",
|
|
54
59
|
"DataTable.vue", "Drawer.vue", "EmptyState.vue", "Field.vue", "Icon.vue", "IconPicker.vue", "Input.vue",
|
|
55
|
-
"ListFilter.vue", "
|
|
60
|
+
"ListFilter.vue", "RelativeDate.vue", "Select.vue", "Textarea.vue", "ToastHost.vue",
|
|
56
61
|
] as const;
|
package/src/tooltip.css
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
align-items: center;
|
|
9
9
|
gap: 8px;
|
|
10
10
|
max-width: 280px;
|
|
11
|
-
padding: 6px 10px
|
|
12
|
-
border:
|
|
11
|
+
padding: 7px 11px; /* 6px 10px plus the 1px the ring stands in for */
|
|
12
|
+
/* Inset ring, not a border: a host's unlayered reset (Tailwind 3's
|
|
13
|
+
preflight) beats this layered rule on border-width, never box-shadow. */
|
|
14
|
+
border: 0;
|
|
13
15
|
border-radius: var(--wp-radius);
|
|
14
16
|
background: var(--wp-bg-3);
|
|
15
17
|
color: var(--wp-text);
|
|
16
|
-
box-shadow: var(--wp-shadow-md);
|
|
18
|
+
box-shadow: inset 0 0 0 1px var(--wp-border-strong), var(--wp-shadow-md);
|
|
17
19
|
font: 500 12px/1.4 var(--wp-font-sans);
|
|
18
20
|
pointer-events: none;
|
|
19
21
|
white-space: normal;
|
|
@@ -29,9 +31,10 @@
|
|
|
29
31
|
}
|
|
30
32
|
.wp-tooltip__kbd {
|
|
31
33
|
flex-shrink: 0;
|
|
32
|
-
padding:
|
|
33
|
-
border:
|
|
34
|
+
padding: 2px 7px;
|
|
35
|
+
border: 0;
|
|
34
36
|
border-radius: var(--wp-radius-sm);
|
|
37
|
+
box-shadow: inset 0 0 0 1px var(--wp-border-strong);
|
|
35
38
|
background: var(--wp-bg-1);
|
|
36
39
|
color: var(--wp-text-muted);
|
|
37
40
|
font: 500 11px/16px var(--wp-font-mono);
|
package/src/useDialogFocus.ts
CHANGED
|
@@ -18,11 +18,18 @@ export interface DialogFocusOptions {
|
|
|
18
18
|
onEscape: () => void;
|
|
19
19
|
/** Where focus goes on open; defaults to the first control. */
|
|
20
20
|
initial?: (root: HTMLElement, controls: HTMLElement[]) => HTMLElement | null | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Listen for keys on `window` (default). Off for a panel inside ModalShell,
|
|
23
|
+
* whose key shield stops keys at the overlay: call the returned `onKeydown`
|
|
24
|
+
* from the shell's `keydown` event instead.
|
|
25
|
+
*/
|
|
26
|
+
window?: boolean | undefined;
|
|
21
27
|
/** Keep Tab inside the panel (default). Off for a panel beside the page. */
|
|
22
28
|
trap?: () => boolean;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
export function useDialogFocus(panel: Ref<HTMLElement | null>, isOpen: () => boolean, opts: DialogFocusOptions): void {
|
|
31
|
+
export function useDialogFocus(panel: Ref<HTMLElement | null>, isOpen: () => boolean, opts: DialogFocusOptions): { onKeydown: (e: KeyboardEvent) => void } {
|
|
32
|
+
const onWindow = opts.window !== false;
|
|
26
33
|
let returnTo: HTMLElement | null = null;
|
|
27
34
|
|
|
28
35
|
function onKeydown(e: KeyboardEvent): void {
|
|
@@ -57,7 +64,7 @@ export function useDialogFocus(panel: Ref<HTMLElement | null>, isOpen: () => boo
|
|
|
57
64
|
watch(isOpen, (v, was) => {
|
|
58
65
|
if (typeof window === "undefined") return;
|
|
59
66
|
if (v) {
|
|
60
|
-
window.addEventListener("keydown", onKeydown);
|
|
67
|
+
if (onWindow) window.addEventListener("keydown", onKeydown);
|
|
61
68
|
void opened();
|
|
62
69
|
} else {
|
|
63
70
|
window.removeEventListener("keydown", onKeydown);
|
|
@@ -70,4 +77,6 @@ export function useDialogFocus(panel: Ref<HTMLElement | null>, isOpen: () => boo
|
|
|
70
77
|
window.removeEventListener("keydown", onKeydown);
|
|
71
78
|
if (isOpen()) closed();
|
|
72
79
|
});
|
|
80
|
+
|
|
81
|
+
return { onKeydown };
|
|
73
82
|
}
|
package/src/CloseButton.vue
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Close button: the one × that closes a dialog, drawer, panel or banner. A
|
|
4
|
-
* square, quiet icon button on the 24/32/40 height scale, named by `label`
|
|
5
|
-
* for screen readers and shown as its tooltip. Works on the canvas
|
|
6
|
-
* (`size="sm"`) and in the manager (`md`).
|
|
7
|
-
*
|
|
8
|
-
* <CloseButton @click="close" />
|
|
9
|
-
* <CloseButton label="Dismiss" size="sm" @click="hide" />
|
|
10
|
-
*
|
|
11
|
-
* Manager icon actions other than closing use `Button` with an icon and no
|
|
12
|
-
* text.
|
|
13
|
-
*/
|
|
14
|
-
import { vTooltip } from "./tooltip";
|
|
15
|
-
|
|
16
|
-
withDefaults(defineProps<{
|
|
17
|
-
label?: string | undefined;
|
|
18
|
-
size?: "sm" | "md" | "lg" | undefined;
|
|
19
|
-
disabled?: boolean | undefined;
|
|
20
|
-
/** A shortcut for the tooltip, e.g. "Esc". */
|
|
21
|
-
kbd?: string | undefined;
|
|
22
|
-
}>(), { label: "Close", size: "md", kbd: undefined });
|
|
23
|
-
|
|
24
|
-
const emit = defineEmits<{ (e: "click", ev: MouseEvent): void }>();
|
|
25
|
-
</script>
|
|
26
|
-
|
|
27
|
-
<template>
|
|
28
|
-
<button
|
|
29
|
-
v-tooltip="{ text: label, kbd }"
|
|
30
|
-
type="button"
|
|
31
|
-
class="wp-close-btn"
|
|
32
|
-
:data-size="size"
|
|
33
|
-
:aria-label="label"
|
|
34
|
-
:disabled="disabled"
|
|
35
|
-
@click="emit('click', $event)"
|
|
36
|
-
>
|
|
37
|
-
<i class="pi pi-times" aria-hidden="true" />
|
|
38
|
-
</button>
|
|
39
|
-
</template>
|
|
40
|
-
|
|
41
|
-
<style scoped>
|
|
42
|
-
.wp-close-btn {
|
|
43
|
-
display: inline-grid;
|
|
44
|
-
place-items: center;
|
|
45
|
-
flex: none;
|
|
46
|
-
width: var(--wp-control-h-md);
|
|
47
|
-
height: var(--wp-control-h-md);
|
|
48
|
-
margin: 0;
|
|
49
|
-
padding: 0;
|
|
50
|
-
border: 1px solid transparent;
|
|
51
|
-
border-radius: var(--wp-radius);
|
|
52
|
-
background: transparent;
|
|
53
|
-
color: var(--wp-text-muted);
|
|
54
|
-
cursor: pointer;
|
|
55
|
-
transition: background var(--wp-motion-hover), color var(--wp-motion-hover), border-color var(--wp-motion-hover);
|
|
56
|
-
}
|
|
57
|
-
.wp-close-btn[data-size="sm"] { width: var(--wp-control-h-sm); height: var(--wp-control-h-sm); border-radius: var(--wp-radius-sm); }
|
|
58
|
-
.wp-close-btn[data-size="lg"] { width: var(--wp-control-h-lg); height: var(--wp-control-h-lg); }
|
|
59
|
-
.wp-close-btn .pi { font-size: 12px; }
|
|
60
|
-
.wp-close-btn[data-size="sm"] .pi { font-size: 10px; }
|
|
61
|
-
.wp-close-btn[data-size="lg"] .pi { font-size: 14px; }
|
|
62
|
-
.wp-close-btn:hover:not(:disabled) { background: var(--wp-bg-3); color: var(--wp-text); border-color: var(--wp-border); }
|
|
63
|
-
.wp-close-btn:active:not(:disabled) { background: var(--wp-bg-4); }
|
|
64
|
-
.wp-close-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp-border-focus); }
|
|
65
|
-
.wp-close-btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
66
|
-
@media (prefers-reduced-motion: reduce) {
|
|
67
|
-
.wp-close-btn { transition: none; }
|
|
68
|
-
}
|
|
69
|
-
</style>
|
package/src/ColorPicker.vue
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, ref, watch } from "vue";
|
|
3
|
-
import Popover from "./Popover.vue";
|
|
4
|
-
import Input from "./Input.vue";
|
|
5
|
-
import HsvPicker from "./HsvPicker.vue";
|
|
6
|
-
import { COLOUR_PRESETS } from "./colour-presets";
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
modelValue: string;
|
|
10
|
-
presets?: string[] | undefined;
|
|
11
|
-
ariaLabel?: string | undefined;
|
|
12
|
-
/** Swatch and chevron only, for a tight spot such as a name row. */
|
|
13
|
-
compact?: boolean | undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
-
presets: () => [...COLOUR_PRESETS],
|
|
18
|
-
ariaLabel: undefined,
|
|
19
|
-
compact: false,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const emit = defineEmits<{
|
|
23
|
-
"update:modelValue": [value: string];
|
|
24
|
-
}>();
|
|
25
|
-
|
|
26
|
-
const HEX_RE = /^#[0-9a-fA-F]{6}$/;
|
|
27
|
-
|
|
28
|
-
const open = ref(false);
|
|
29
|
-
const hexDraft = ref(props.modelValue);
|
|
30
|
-
|
|
31
|
-
watch(() => props.modelValue, (v) => {
|
|
32
|
-
hexDraft.value = v;
|
|
33
|
-
});
|
|
34
|
-
// A fresh draft each time it opens, so a half-typed hex doesn't linger.
|
|
35
|
-
watch(open, (v) => { if (v) hexDraft.value = props.modelValue; });
|
|
36
|
-
|
|
37
|
-
const swatchColor = computed(() => (HEX_RE.test(props.modelValue) ? props.modelValue : "transparent"));
|
|
38
|
-
const shownHex = computed(() => (HEX_RE.test(props.modelValue) ? props.modelValue.toLowerCase() : "No colour"));
|
|
39
|
-
|
|
40
|
-
const swatchAriaLabel = computed(
|
|
41
|
-
() => props.ariaLabel ?? `Pick color, current ${props.modelValue}`,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
function close() {
|
|
45
|
-
open.value = false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function selectPreset(preset: string) {
|
|
49
|
-
emit("update:modelValue", preset);
|
|
50
|
-
close();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function onHexInput(value: string) {
|
|
54
|
-
hexDraft.value = value;
|
|
55
|
-
if (HEX_RE.test(value)) {
|
|
56
|
-
emit("update:modelValue", value);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function isActivePreset(preset: string): boolean {
|
|
61
|
-
return preset.toLowerCase() === props.modelValue.toLowerCase();
|
|
62
|
-
}
|
|
63
|
-
</script>
|
|
64
|
-
|
|
65
|
-
<template>
|
|
66
|
-
<!-- A swatch button that says what it holds (colour, hex, chevron), on the
|
|
67
|
-
kit Popover so the panel follows it on scroll and closes on Escape or
|
|
68
|
-
a click outside. -->
|
|
69
|
-
<Popover v-model:open="open" :label="swatchAriaLabel">
|
|
70
|
-
<template #trigger="{ props: triggerProps }">
|
|
71
|
-
<button
|
|
72
|
-
v-bind="triggerProps"
|
|
73
|
-
type="button"
|
|
74
|
-
class="wp-color-picker__trigger"
|
|
75
|
-
:class="{ 'wp-color-picker__trigger--compact': compact }"
|
|
76
|
-
:aria-label="swatchAriaLabel"
|
|
77
|
-
data-test="color-trigger"
|
|
78
|
-
>
|
|
79
|
-
<span
|
|
80
|
-
class="wp-color-picker__swatch"
|
|
81
|
-
:style="{ background: swatchColor }"
|
|
82
|
-
data-test="color-swatch"
|
|
83
|
-
aria-hidden="true"
|
|
84
|
-
/>
|
|
85
|
-
<span v-if="!compact" class="wp-color-picker__value" aria-hidden="true">{{ shownHex }}</span>
|
|
86
|
-
<i class="pi pi-chevron-down wp-color-picker__caret" aria-hidden="true" />
|
|
87
|
-
</button>
|
|
88
|
-
</template>
|
|
89
|
-
<div class="wp-color-picker__popover">
|
|
90
|
-
<div class="wp-color-picker__row">
|
|
91
|
-
<Input
|
|
92
|
-
:model-value="hexDraft"
|
|
93
|
-
aria-label="Hex color value"
|
|
94
|
-
class="wp-color-picker__hex"
|
|
95
|
-
data-test="color-hex-input"
|
|
96
|
-
@update:model-value="(v) => onHexInput(String(v ?? ''))"
|
|
97
|
-
/>
|
|
98
|
-
</div>
|
|
99
|
-
<HsvPicker
|
|
100
|
-
:model-value="props.modelValue"
|
|
101
|
-
:aria-label="swatchAriaLabel"
|
|
102
|
-
@update:model-value="(v) => emit('update:modelValue', v)"
|
|
103
|
-
/>
|
|
104
|
-
<div class="wp-color-picker__palette" role="listbox" aria-label="Preset colors">
|
|
105
|
-
<button
|
|
106
|
-
v-for="preset in props.presets"
|
|
107
|
-
:key="preset"
|
|
108
|
-
type="button"
|
|
109
|
-
class="wp-color-picker__chip"
|
|
110
|
-
:style="{ background: preset }"
|
|
111
|
-
:title="preset"
|
|
112
|
-
:data-active="isActivePreset(preset)"
|
|
113
|
-
:aria-label="`Use preset ${preset}`"
|
|
114
|
-
data-test="color-preset"
|
|
115
|
-
@click="selectPreset(preset)"
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</Popover>
|
|
120
|
-
</template>
|
|
121
|
-
|
|
122
|
-
<style scoped>
|
|
123
|
-
.wp-color-picker__trigger {
|
|
124
|
-
display: inline-flex;
|
|
125
|
-
align-items: center;
|
|
126
|
-
gap: var(--wp-space-3);
|
|
127
|
-
height: var(--wp-control-h-md, 32px);
|
|
128
|
-
min-width: 128px;
|
|
129
|
-
padding: 0 var(--wp-space-4) 0 var(--wp-space-2);
|
|
130
|
-
background: var(--wp-bg-2);
|
|
131
|
-
border: 1px solid var(--wp-border);
|
|
132
|
-
border-radius: var(--wp-radius-sm);
|
|
133
|
-
color: var(--wp-text);
|
|
134
|
-
font: inherit;
|
|
135
|
-
cursor: pointer;
|
|
136
|
-
transition: border-color 0.12s, box-shadow 0.12s;
|
|
137
|
-
}
|
|
138
|
-
.wp-color-picker__trigger--compact { min-width: 0; padding: 0 var(--wp-space-3) 0 var(--wp-space-2); gap: var(--wp-space-2); }
|
|
139
|
-
.wp-color-picker__trigger:hover { border-color: var(--wp-border-strong); }
|
|
140
|
-
.wp-color-picker__trigger:focus-visible { outline: none; border-color: var(--wp-accent-500); box-shadow: var(--wp-focus-ring); }
|
|
141
|
-
.wp-color-picker__swatch {
|
|
142
|
-
width: 20px;
|
|
143
|
-
height: 20px;
|
|
144
|
-
flex-shrink: 0;
|
|
145
|
-
border-radius: min(4px, var(--wp-radius-cap, 999px));
|
|
146
|
-
/* An inner ring keeps a dark swatch visible on a dark field. */
|
|
147
|
-
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--wp-text) 22%, transparent);
|
|
148
|
-
}
|
|
149
|
-
.wp-color-picker__value {
|
|
150
|
-
flex: 1;
|
|
151
|
-
text-align: left;
|
|
152
|
-
font-family: var(--wp-font-mono);
|
|
153
|
-
font-size: 12px;
|
|
154
|
-
color: var(--wp-text-muted);
|
|
155
|
-
}
|
|
156
|
-
.wp-color-picker__caret { font-size: 9px; color: var(--wp-text-dim); }
|
|
157
|
-
|
|
158
|
-
.wp-color-picker__popover {
|
|
159
|
-
width: 220px;
|
|
160
|
-
padding: var(--wp-space-2);
|
|
161
|
-
display: flex;
|
|
162
|
-
flex-direction: column;
|
|
163
|
-
gap: var(--wp-space-5);
|
|
164
|
-
}
|
|
165
|
-
.wp-color-picker__row {
|
|
166
|
-
display: flex;
|
|
167
|
-
align-items: center;
|
|
168
|
-
gap: var(--wp-space-4);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.wp-color-picker__hex {
|
|
172
|
-
flex: 1;
|
|
173
|
-
min-width: 0;
|
|
174
|
-
font-family: var(--wp-font-mono);
|
|
175
|
-
font-size: var(--wp-text-sm);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.wp-color-picker__palette {
|
|
179
|
-
display: grid;
|
|
180
|
-
grid-template-columns: repeat(4, 1fr);
|
|
181
|
-
gap: var(--wp-space-3);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.wp-color-picker__chip {
|
|
185
|
-
/* Span the grid cell instead of locking at 32px so the swatches reach
|
|
186
|
-
* both edges of the popover. aspect-ratio keeps them square as the
|
|
187
|
-
* cell width changes with popover size. */
|
|
188
|
-
width: 100%;
|
|
189
|
-
aspect-ratio: 1 / 1;
|
|
190
|
-
min-width: 0;
|
|
191
|
-
border-radius: var(--wp-radius-sm);
|
|
192
|
-
border: 1px solid var(--wp-border);
|
|
193
|
-
cursor: pointer;
|
|
194
|
-
padding: 0;
|
|
195
|
-
transition: transform 0.08s, box-shadow 0.12s;
|
|
196
|
-
}
|
|
197
|
-
.wp-color-picker__chip:hover { transform: scale(1.08); }
|
|
198
|
-
.wp-color-picker__chip[data-active="true"] {
|
|
199
|
-
box-shadow: 0 0 0 2px var(--wp-bg-2), 0 0 0 4px var(--wp-accent-500);
|
|
200
|
-
}
|
|
201
|
-
</style>
|
package/src/ConfirmDialog.vue
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Themed confirmation dialog — replaces `window.confirm()` for any
|
|
4
|
-
* action that needs an in-app prompt (reset overrides, save-to-library,
|
|
5
|
-
* reset-from-library, etc.). Consumers control it via v-model:visible
|
|
6
|
-
* plus title/body/confirm-label slots; the dialog emits `confirm` /
|
|
7
|
-
* `cancel` and closes itself on Esc + overlay click + Cancel button.
|
|
8
|
-
*
|
|
9
|
-
* Variants:
|
|
10
|
-
* - default: accent-coloured Confirm button (constructive actions)
|
|
11
|
-
* - danger: red Confirm button (destructive actions, e.g. discard,
|
|
12
|
-
* reset overrides)
|
|
13
|
-
*/
|
|
14
|
-
import { onBeforeUnmount, watch } from "vue";
|
|
15
|
-
import WpButton from "./WpButton.vue";
|
|
16
|
-
|
|
17
|
-
const props = withDefaults(
|
|
18
|
-
defineProps<{
|
|
19
|
-
visible: boolean;
|
|
20
|
-
title: string;
|
|
21
|
-
body?: string | undefined;
|
|
22
|
-
confirmLabel?: string | undefined;
|
|
23
|
-
cancelLabel?: string | undefined;
|
|
24
|
-
variant?: "default" | "danger" | undefined;
|
|
25
|
-
}>(),
|
|
26
|
-
{
|
|
27
|
-
body: "",
|
|
28
|
-
confirmLabel: "Confirm",
|
|
29
|
-
cancelLabel: "Cancel",
|
|
30
|
-
variant: "default",
|
|
31
|
-
},
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const emit = defineEmits<{
|
|
35
|
-
"confirm": [];
|
|
36
|
-
"cancel": [];
|
|
37
|
-
}>();
|
|
38
|
-
|
|
39
|
-
function onConfirm(): void { emit("confirm"); }
|
|
40
|
-
function onCancel(): void { emit("cancel"); }
|
|
41
|
-
|
|
42
|
-
function onKeydown(ev: KeyboardEvent): void {
|
|
43
|
-
if (!props.visible) return;
|
|
44
|
-
if (ev.key === "Escape") {
|
|
45
|
-
ev.preventDefault();
|
|
46
|
-
emit("cancel");
|
|
47
|
-
} else if (ev.key === "Enter" && !ev.shiftKey) {
|
|
48
|
-
ev.preventDefault();
|
|
49
|
-
emit("confirm");
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
watch(
|
|
54
|
-
() => props.visible,
|
|
55
|
-
(visible) => {
|
|
56
|
-
if (visible) window.addEventListener("keydown", onKeydown);
|
|
57
|
-
else window.removeEventListener("keydown", onKeydown);
|
|
58
|
-
},
|
|
59
|
-
{ immediate: true },
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
onBeforeUnmount(() => window.removeEventListener("keydown", onKeydown));
|
|
63
|
-
</script>
|
|
64
|
-
|
|
65
|
-
<template>
|
|
66
|
-
<Teleport to="body">
|
|
67
|
-
<Transition name="wp-modal" appear>
|
|
68
|
-
<!-- The backdrop is a click target only; keyboard users close with Escape. -->
|
|
69
|
-
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events, vuejs-accessibility/no-static-element-interactions -->
|
|
70
|
-
<div
|
|
71
|
-
v-if="visible"
|
|
72
|
-
class="wp-confirm-overlay"
|
|
73
|
-
data-test="confirm-overlay"
|
|
74
|
-
@click="onCancel"
|
|
75
|
-
>
|
|
76
|
-
<div
|
|
77
|
-
class="wp-confirm"
|
|
78
|
-
role="alertdialog"
|
|
79
|
-
aria-modal="true"
|
|
80
|
-
:class="`wp-confirm--${variant}`"
|
|
81
|
-
@click.stop
|
|
82
|
-
>
|
|
83
|
-
<div class="wp-confirm__head">
|
|
84
|
-
<i
|
|
85
|
-
class="pi"
|
|
86
|
-
:class="variant === 'danger' ? 'pi-exclamation-triangle' : 'pi-info-circle'"
|
|
87
|
-
aria-hidden="true"
|
|
88
|
-
/>
|
|
89
|
-
<h3 class="wp-confirm__title" data-test="confirm-title">{{ title }}</h3>
|
|
90
|
-
</div>
|
|
91
|
-
<p v-if="body" class="wp-confirm__body" data-test="confirm-body">{{ body }}</p>
|
|
92
|
-
<div class="wp-confirm__foot">
|
|
93
|
-
<WpButton data-test="confirm-cancel" @click="onCancel">{{ cancelLabel }}</WpButton>
|
|
94
|
-
<WpButton :variant="variant === 'danger' ? 'danger' : 'primary'" data-test="confirm-confirm" @click="onConfirm">{{ confirmLabel }}</WpButton>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</Transition>
|
|
99
|
-
</Teleport>
|
|
100
|
-
</template>
|
|
101
|
-
|
|
102
|
-
<style>
|
|
103
|
-
/* Shared modal open/close transition — overlay fade + panel scale.
|
|
104
|
-
* See `_modal-motion.css` for rules. Imported unscoped so the
|
|
105
|
-
* `wp-modal-*` transition class names match the Vue-emitted classes
|
|
106
|
-
* cleanly (named transitions don't survive Vue's `data-v-...`
|
|
107
|
-
* scoping). */
|
|
108
|
-
@import "./_modal-motion.css";
|
|
109
|
-
</style>
|
|
110
|
-
|
|
111
|
-
<style scoped>
|
|
112
|
-
.wp-confirm-overlay {
|
|
113
|
-
position: fixed;
|
|
114
|
-
inset: 0;
|
|
115
|
-
z-index: 10000;
|
|
116
|
-
display: flex;
|
|
117
|
-
align-items: center;
|
|
118
|
-
justify-content: center;
|
|
119
|
-
padding: 20px;
|
|
120
|
-
background: var(--wp-overlay-bg, rgba(0, 0, 0, 0.62));
|
|
121
|
-
font-family: var(--wp-font-sans, sans-serif);
|
|
122
|
-
}
|
|
123
|
-
.wp-confirm {
|
|
124
|
-
background: var(--wp-bg-2);
|
|
125
|
-
border: 1px solid var(--wp-border);
|
|
126
|
-
border-radius: var(--wp-radius);
|
|
127
|
-
width: 380px;
|
|
128
|
-
max-width: 100%;
|
|
129
|
-
color: var(--wp-text);
|
|
130
|
-
box-shadow: var(--wp-shadow-lg, 0 12px 40px rgba(0, 0, 0, 0.5));
|
|
131
|
-
overflow: hidden;
|
|
132
|
-
}
|
|
133
|
-
.wp-confirm__head {
|
|
134
|
-
display: flex;
|
|
135
|
-
align-items: center;
|
|
136
|
-
gap: 10px;
|
|
137
|
-
padding: 14px 16px 8px;
|
|
138
|
-
}
|
|
139
|
-
.wp-confirm__head .pi {
|
|
140
|
-
font-size: 16px;
|
|
141
|
-
color: var(--wp-accent-text, var(--wp-text));
|
|
142
|
-
}
|
|
143
|
-
.wp-confirm--danger .wp-confirm__head .pi {
|
|
144
|
-
color: var(--wp-danger, var(--wp-red));
|
|
145
|
-
}
|
|
146
|
-
.wp-confirm__title {
|
|
147
|
-
font: 600 13px var(--wp-font-sans);
|
|
148
|
-
color: var(--wp-text);
|
|
149
|
-
margin: 0;
|
|
150
|
-
}
|
|
151
|
-
.wp-confirm__body {
|
|
152
|
-
font: 12px/1.5 var(--wp-font-sans);
|
|
153
|
-
color: var(--wp-text-muted);
|
|
154
|
-
margin: 0;
|
|
155
|
-
padding: 0 16px 14px;
|
|
156
|
-
}
|
|
157
|
-
.wp-confirm__foot {
|
|
158
|
-
display: flex;
|
|
159
|
-
justify-content: flex-end;
|
|
160
|
-
gap: 8px;
|
|
161
|
-
padding: 10px 14px;
|
|
162
|
-
background: var(--wp-bg-3);
|
|
163
|
-
border-top: 1px solid var(--wp-border);
|
|
164
|
-
}
|
|
165
|
-
</style>
|