@dumiflex/wp-kit 0.3.0 → 0.5.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 +92 -51
- package/LICENSE +674 -674
- package/README.md +73 -76
- package/package.json +78 -77
- package/src/Avatar.vue +83 -83
- package/src/Badge.vue +97 -97
- package/src/Banner.vue +118 -118
- package/src/BookmarkButton.vue +27 -27
- package/src/BundleMeta.vue +51 -51
- package/src/Button.vue +103 -103
- package/src/Card.vue +68 -68
- package/src/Checkbox.vue +109 -107
- package/src/Chip.vue +163 -97
- package/src/ChoiceCards.vue +133 -133
- package/src/CloseButton.vue +69 -69
- package/src/CodeBlock.vue +146 -119
- package/src/ColorField.vue +188 -206
- package/src/ColorPicker.vue +201 -201
- package/src/Combobox.vue +331 -334
- package/src/ConfirmDialog.vue +165 -163
- package/src/ConfirmModal.vue +147 -147
- package/src/ConstraintMatrixPreview.vue +708 -708
- package/src/ContextMenu.vue +386 -379
- package/src/CopyButton.vue +156 -156
- package/src/DataTable.vue +293 -292
- package/src/DatePicker.vue +234 -234
- package/src/DescriptionList.vue +73 -73
- package/src/DiffView.vue +68 -68
- package/src/Disclosure.vue +166 -166
- package/src/DragHandle.vue +49 -49
- package/src/Drawer.vue +131 -131
- package/src/DropZone.vue +196 -196
- package/src/DropdownMenu.vue +72 -72
- package/src/EmptyState.vue +111 -111
- package/src/Field.vue +28 -28
- package/src/HsvPicker.vue +208 -208
- package/src/Icon.vue +39 -39
- package/src/IconPicker.vue +209 -209
- package/src/ImageGrid.vue +127 -125
- package/src/InlineEdit.vue +183 -183
- package/src/Input.vue +99 -99
- package/src/Kbd.vue +56 -56
- package/src/Lightbox.vue +176 -173
- package/src/ListFilter.vue +85 -85
- package/src/ListRow.vue +204 -204
- package/src/LoadingMark.vue +217 -161
- package/src/Logo.vue +60 -60
- package/src/Modal.vue +78 -76
- package/src/ModalShell.vue +109 -107
- package/src/Pagination.vue +106 -106
- package/src/Popover.vue +180 -171
- package/src/PostCard.vue +210 -211
- package/src/ProgressBar.vue +90 -90
- package/src/RadioGroup.vue +177 -177
- package/src/RangeSlider.vue +183 -183
- package/src/RelativeDate.vue +35 -35
- package/src/ResizeHandle.vue +143 -143
- package/src/SearchInput.vue +192 -199
- package/src/Select.vue +644 -641
- package/src/Separator.vue +54 -54
- package/src/Skeleton.vue +98 -82
- package/src/Slider.vue +198 -198
- package/src/SortableList.vue +260 -260
- package/src/SplitPane.vue +68 -68
- package/src/StarButton.vue +40 -40
- package/src/Stepper.vue +141 -141
- package/src/SupportLinks.vue +93 -93
- package/src/TagInput.vue +297 -300
- package/src/Textarea.vue +57 -57
- package/src/Timeline.vue +92 -92
- package/src/ToastCard.vue +119 -119
- package/src/ToastHost.vue +40 -40
- package/src/Toggle.vue +36 -36
- package/src/TokenizedValue.vue +310 -310
- package/src/Toolbar.vue +110 -110
- package/src/VirtualList.vue +87 -87
- package/src/VisuallyHidden.vue +29 -29
- package/src/WpButton.vue +75 -75
- package/src/WpCheck.vue +104 -102
- package/src/WpIconButton.vue +107 -107
- package/src/WpImageZoom.vue +544 -544
- package/src/WpNumber.vue +178 -183
- package/src/WpSegmented.vue +175 -175
- package/src/WpSelect.vue +93 -89
- package/src/WpSwitch.vue +163 -163
- package/src/WpTabs.vue +188 -188
- package/src/_modal-motion.css +33 -33
- package/src/_skeleton.css +50 -50
- package/src/axis-color.ts +42 -42
- package/src/button.css +169 -169
- package/src/calendar.ts +59 -59
- package/src/clipboard.ts +50 -50
- package/src/colour-presets.ts +12 -12
- package/src/diff.ts +51 -51
- package/src/drop-zone.ts +41 -41
- package/src/field.css +50 -0
- package/src/flip.ts +237 -237
- package/src/floating-layer.ts +48 -0
- package/src/floating.ts +99 -99
- package/src/highlight.ts +199 -0
- package/src/hsv.ts +100 -100
- package/src/icon-catalog.ts +52 -52
- package/src/image-zoom.ts +93 -93
- package/src/kind-colour.ts +28 -0
- package/src/loading-mark-motion.ts +249 -0
- package/src/logo-paths.ts +21 -21
- package/src/matrix-axis.ts +91 -91
- package/src/pagination.ts +27 -27
- package/src/platform-keys.ts +29 -29
- package/src/rich-tokenize.ts +357 -357
- package/src/select-types.ts +32 -32
- package/src/slider-math.ts +45 -45
- package/src/sortable.ts +22 -22
- package/src/theme/fonts/OFL-Fraunces.txt +93 -93
- package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -93
- package/src/theme/fonts/OFL-Manrope.txt +93 -93
- package/src/theme/looks.css +428 -428
- package/src/theme/palettes.css +254 -254
- package/src/theme/tokens.css +2896 -2894
- package/src/tokens.ts +56 -56
- package/src/tooltip.css +43 -43
- package/src/tooltip.ts +146 -146
- package/src/useDialogFocus.ts +73 -73
- package/src/useGrowableField.ts +357 -357
- package/src/useModalKeyShield.ts +116 -116
- package/src/useToast.ts +48 -48
- package/src/wp-logo.svg +14 -14
- package/src/ChipToggle.vue +0 -92
- package/src/KindChip.vue +0 -53
- package/src/RatingBadge.vue +0 -42
package/src/Badge.vue
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Badge: a count bubble or a status dot. On its own it sits in the line of
|
|
4
|
-
* text; wrapped around something (a button, an icon, an avatar) it sits on
|
|
5
|
-
* that thing's top-right corner. Works on the canvas and in the manager.
|
|
6
|
-
*
|
|
7
|
-
* <Badge :count="3" tone="danger" label="3 conflicts"><Button icon="pi-bell" aria-label="Problems" /></Badge>
|
|
8
|
-
* <Badge dot tone="success" text="Running" />
|
|
9
|
-
* <Badge dot tone="warn" label="Out of date" />
|
|
10
|
-
*
|
|
11
|
-
* Colour is never the only signal: a dot without `text` needs a `label`,
|
|
12
|
-
* which screen readers read in its place. Counts over `max` read
|
|
13
|
-
* "99+". A count of 0 hides the badge unless `show-zero`.
|
|
14
|
-
*/
|
|
15
|
-
import { computed, useSlots } from "vue";
|
|
16
|
-
|
|
17
|
-
const props = withDefaults(defineProps<{
|
|
18
|
-
count?: number | undefined;
|
|
19
|
-
/** Counts above this read "max+". */
|
|
20
|
-
max?: number | undefined;
|
|
21
|
-
/** A dot instead of a number. */
|
|
22
|
-
dot?: boolean | undefined;
|
|
23
|
-
tone?: "neutral" | "info" | "success" | "warn" | "danger" | "accent" | undefined;
|
|
24
|
-
/** Visible text after the dot or count (a status line). */
|
|
25
|
-
text?: string | undefined;
|
|
26
|
-
/** Name for screen readers when the badge has no text, e.g. "3 conflicts". */
|
|
27
|
-
label?: string | undefined;
|
|
28
|
-
/** Show a count of 0 instead of hiding the badge. */
|
|
29
|
-
showZero?: boolean | undefined;
|
|
30
|
-
/** Draw the dot as a soft pulse (something is running). */
|
|
31
|
-
pulse?: boolean | undefined;
|
|
32
|
-
}>(), { count: undefined, max: 99, tone: "accent", text: undefined, label: undefined });
|
|
33
|
-
|
|
34
|
-
const slots = useSlots();
|
|
35
|
-
const anchored = computed(() => !!slots.default);
|
|
36
|
-
const shown = computed(() => props.dot || (props.count !== undefined && (props.count > 0 || props.showZero)));
|
|
37
|
-
const countText = computed(() =>
|
|
38
|
-
props.count === undefined ? "" : props.count > props.max ? `${props.max}+` : String(props.count),
|
|
39
|
-
);
|
|
40
|
-
</script>
|
|
41
|
-
|
|
42
|
-
<template>
|
|
43
|
-
<span v-if="anchored" class="wp-badge-anchor">
|
|
44
|
-
<slot />
|
|
45
|
-
<span v-if="shown" class="wp-badge wp-badge--corner" :data-tone="tone" :data-dot="dot || undefined" :data-pulse="pulse || undefined" aria-hidden="true"><template v-if="!dot">{{ countText }}</template></span>
|
|
46
|
-
<span v-if="shown && (label || countText)" class="wp-badge__sr">{{ label ?? countText }}</span>
|
|
47
|
-
</span>
|
|
48
|
-
<span v-else-if="shown || text" class="wp-badge-line" :data-tone="tone">
|
|
49
|
-
<span v-if="shown" class="wp-badge" :data-tone="tone" :data-dot="dot || undefined" :data-pulse="pulse || undefined" aria-hidden="true"><template v-if="!dot">{{ countText }}</template></span>
|
|
50
|
-
<span v-if="!text && (label || countText)" class="wp-badge__sr">{{ label ?? countText }}</span>
|
|
51
|
-
<span v-if="text" class="wp-badge-line__text">{{ text }}</span>
|
|
52
|
-
</span>
|
|
53
|
-
</template>
|
|
54
|
-
|
|
55
|
-
<style scoped>
|
|
56
|
-
.wp-badge-anchor { position: relative; display: inline-flex; }
|
|
57
|
-
.wp-badge__sr {
|
|
58
|
-
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
|
|
59
|
-
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
60
|
-
}
|
|
61
|
-
.wp-badge-line {
|
|
62
|
-
display: inline-flex; align-items: center; gap: var(--wp-space-2); min-width: 0;
|
|
63
|
-
color: var(--wp-text-muted); font: 500 var(--wp-text-xs)/1.2 var(--wp-font-sans);
|
|
64
|
-
}
|
|
65
|
-
.wp-badge-line__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
66
|
-
.wp-badge {
|
|
67
|
-
--_tone: var(--wp-accent);
|
|
68
|
-
display: inline-grid; place-items: center; flex: none; box-sizing: border-box;
|
|
69
|
-
min-width: 18px; height: 18px; padding: 0 5px;
|
|
70
|
-
border-radius: var(--wp-radius-cap, 999px); background: var(--_tone); color: var(--wp-bg);
|
|
71
|
-
font: 600 10.5px/1 var(--wp-font-sans); font-variant-numeric: tabular-nums;
|
|
72
|
-
}
|
|
73
|
-
.wp-badge[data-tone="neutral"] { --_tone: var(--wp-text-dim); }
|
|
74
|
-
.wp-badge[data-tone="info"] { --_tone: var(--wp-info); }
|
|
75
|
-
.wp-badge[data-tone="success"] { --_tone: var(--wp-success); }
|
|
76
|
-
.wp-badge[data-tone="warn"] { --_tone: var(--wp-warn); }
|
|
77
|
-
.wp-badge[data-tone="danger"] { --_tone: var(--wp-danger); }
|
|
78
|
-
.wp-badge[data-dot] { min-width: 0; width: 8px; height: 8px; padding: 0; }
|
|
79
|
-
.wp-badge--corner {
|
|
80
|
-
position: absolute; top: 0; right: 0; transform: translate(40%, -40%);
|
|
81
|
-
box-shadow: 0 0 0 2px var(--wp-bg-1); pointer-events: none;
|
|
82
|
-
}
|
|
83
|
-
.wp-badge--corner[data-dot] { transform: translate(25%, -25%); }
|
|
84
|
-
.wp-badge[data-pulse]::after {
|
|
85
|
-
content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--_tone);
|
|
86
|
-
animation: wp-badge-pulse 1.6s ease-out infinite;
|
|
87
|
-
}
|
|
88
|
-
.wp-badge[data-pulse] { position: relative; }
|
|
89
|
-
.wp-badge--corner[data-pulse] { position: absolute; }
|
|
90
|
-
@keyframes wp-badge-pulse {
|
|
91
|
-
from { opacity: 0.6; transform: scale(1); }
|
|
92
|
-
to { opacity: 0; transform: scale(2.4); }
|
|
93
|
-
}
|
|
94
|
-
@media (prefers-reduced-motion: reduce) {
|
|
95
|
-
.wp-badge[data-pulse]::after { animation: none; display: none; }
|
|
96
|
-
}
|
|
97
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Badge: a count bubble or a status dot. On its own it sits in the line of
|
|
4
|
+
* text; wrapped around something (a button, an icon, an avatar) it sits on
|
|
5
|
+
* that thing's top-right corner. Works on the canvas and in the manager.
|
|
6
|
+
*
|
|
7
|
+
* <Badge :count="3" tone="danger" label="3 conflicts"><Button icon="pi-bell" aria-label="Problems" /></Badge>
|
|
8
|
+
* <Badge dot tone="success" text="Running" />
|
|
9
|
+
* <Badge dot tone="warn" label="Out of date" />
|
|
10
|
+
*
|
|
11
|
+
* Colour is never the only signal: a dot without `text` needs a `label`,
|
|
12
|
+
* which screen readers read in its place. Counts over `max` read
|
|
13
|
+
* "99+". A count of 0 hides the badge unless `show-zero`.
|
|
14
|
+
*/
|
|
15
|
+
import { computed, useSlots } from "vue";
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<{
|
|
18
|
+
count?: number | undefined;
|
|
19
|
+
/** Counts above this read "max+". */
|
|
20
|
+
max?: number | undefined;
|
|
21
|
+
/** A dot instead of a number. */
|
|
22
|
+
dot?: boolean | undefined;
|
|
23
|
+
tone?: "neutral" | "info" | "success" | "warn" | "danger" | "accent" | undefined;
|
|
24
|
+
/** Visible text after the dot or count (a status line). */
|
|
25
|
+
text?: string | undefined;
|
|
26
|
+
/** Name for screen readers when the badge has no text, e.g. "3 conflicts". */
|
|
27
|
+
label?: string | undefined;
|
|
28
|
+
/** Show a count of 0 instead of hiding the badge. */
|
|
29
|
+
showZero?: boolean | undefined;
|
|
30
|
+
/** Draw the dot as a soft pulse (something is running). */
|
|
31
|
+
pulse?: boolean | undefined;
|
|
32
|
+
}>(), { count: undefined, max: 99, tone: "accent", text: undefined, label: undefined });
|
|
33
|
+
|
|
34
|
+
const slots = useSlots();
|
|
35
|
+
const anchored = computed(() => !!slots.default);
|
|
36
|
+
const shown = computed(() => props.dot || (props.count !== undefined && (props.count > 0 || props.showZero)));
|
|
37
|
+
const countText = computed(() =>
|
|
38
|
+
props.count === undefined ? "" : props.count > props.max ? `${props.max}+` : String(props.count),
|
|
39
|
+
);
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<span v-if="anchored" class="wp-badge-anchor">
|
|
44
|
+
<slot />
|
|
45
|
+
<span v-if="shown" class="wp-badge wp-badge--corner" :data-tone="tone" :data-dot="dot || undefined" :data-pulse="pulse || undefined" aria-hidden="true"><template v-if="!dot">{{ countText }}</template></span>
|
|
46
|
+
<span v-if="shown && (label || countText)" class="wp-badge__sr">{{ label ?? countText }}</span>
|
|
47
|
+
</span>
|
|
48
|
+
<span v-else-if="shown || text" class="wp-badge-line" :data-tone="tone">
|
|
49
|
+
<span v-if="shown" class="wp-badge" :data-tone="tone" :data-dot="dot || undefined" :data-pulse="pulse || undefined" aria-hidden="true"><template v-if="!dot">{{ countText }}</template></span>
|
|
50
|
+
<span v-if="!text && (label || countText)" class="wp-badge__sr">{{ label ?? countText }}</span>
|
|
51
|
+
<span v-if="text" class="wp-badge-line__text">{{ text }}</span>
|
|
52
|
+
</span>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<style scoped>
|
|
56
|
+
.wp-badge-anchor { position: relative; display: inline-flex; }
|
|
57
|
+
.wp-badge__sr {
|
|
58
|
+
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
|
|
59
|
+
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
60
|
+
}
|
|
61
|
+
.wp-badge-line {
|
|
62
|
+
display: inline-flex; align-items: center; gap: var(--wp-space-2); min-width: 0;
|
|
63
|
+
color: var(--wp-text-muted); font: 500 var(--wp-text-xs)/1.2 var(--wp-font-sans);
|
|
64
|
+
}
|
|
65
|
+
.wp-badge-line__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
66
|
+
.wp-badge {
|
|
67
|
+
--_tone: var(--wp-accent);
|
|
68
|
+
display: inline-grid; place-items: center; flex: none; box-sizing: border-box;
|
|
69
|
+
min-width: 18px; height: 18px; padding: 0 5px;
|
|
70
|
+
border-radius: var(--wp-radius-cap, 999px); background: var(--_tone); color: var(--wp-bg);
|
|
71
|
+
font: 600 10.5px/1 var(--wp-font-sans); font-variant-numeric: tabular-nums;
|
|
72
|
+
}
|
|
73
|
+
.wp-badge[data-tone="neutral"] { --_tone: var(--wp-text-dim); }
|
|
74
|
+
.wp-badge[data-tone="info"] { --_tone: var(--wp-info); }
|
|
75
|
+
.wp-badge[data-tone="success"] { --_tone: var(--wp-success); }
|
|
76
|
+
.wp-badge[data-tone="warn"] { --_tone: var(--wp-warn); }
|
|
77
|
+
.wp-badge[data-tone="danger"] { --_tone: var(--wp-danger); }
|
|
78
|
+
.wp-badge[data-dot] { min-width: 0; width: 8px; height: 8px; padding: 0; }
|
|
79
|
+
.wp-badge--corner {
|
|
80
|
+
position: absolute; top: 0; right: 0; transform: translate(40%, -40%);
|
|
81
|
+
box-shadow: 0 0 0 2px var(--wp-bg-1); pointer-events: none;
|
|
82
|
+
}
|
|
83
|
+
.wp-badge--corner[data-dot] { transform: translate(25%, -25%); }
|
|
84
|
+
.wp-badge[data-pulse]::after {
|
|
85
|
+
content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--_tone);
|
|
86
|
+
animation: wp-badge-pulse 1.6s ease-out infinite;
|
|
87
|
+
}
|
|
88
|
+
.wp-badge[data-pulse] { position: relative; }
|
|
89
|
+
.wp-badge--corner[data-pulse] { position: absolute; }
|
|
90
|
+
@keyframes wp-badge-pulse {
|
|
91
|
+
from { opacity: 0.6; transform: scale(1); }
|
|
92
|
+
to { opacity: 0; transform: scale(2.4); }
|
|
93
|
+
}
|
|
94
|
+
@media (prefers-reduced-motion: reduce) {
|
|
95
|
+
.wp-badge[data-pulse]::after { animation: none; display: none; }
|
|
96
|
+
}
|
|
97
|
+
</style>
|
package/src/Banner.vue
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Banner: an inline message in a tone, with an optional title, actions and
|
|
4
|
-
* a close button. It sits in the flow of the page (not floating), above or
|
|
5
|
-
* inside whatever it talks about. Works on the canvas and in the manager.
|
|
6
|
-
*
|
|
7
|
-
* <Banner tone="warn" title="2 modules are out of date">
|
|
8
|
-
* Update them before you publish.
|
|
9
|
-
* <template #actions><Button size="sm">Update</Button></template>
|
|
10
|
-
* </Banner>
|
|
11
|
-
*
|
|
12
|
-
* A danger banner is announced at once (`role="alert"`); the others are
|
|
13
|
-
* read politely (`role="status"`). Neither takes focus. `note` is for text
|
|
14
|
-
* that is part of the page (a docs tip): `role="note"`, never announced.
|
|
15
|
-
*/
|
|
16
|
-
import { computed, useSlots } from "vue";
|
|
17
|
-
import CloseButton from "./CloseButton.vue";
|
|
18
|
-
|
|
19
|
-
export type BannerTone = "neutral" | "info" | "success" | "warn" | "danger" | "accent";
|
|
20
|
-
|
|
21
|
-
const TONE_ICON: Record<BannerTone, string> = {
|
|
22
|
-
neutral: "pi-info-circle",
|
|
23
|
-
info: "pi-info-circle",
|
|
24
|
-
success: "pi-check-circle",
|
|
25
|
-
warn: "pi-exclamation-triangle",
|
|
26
|
-
danger: "pi-times-circle",
|
|
27
|
-
accent: "pi-sparkles",
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const props = withDefaults(defineProps<{
|
|
31
|
-
tone?: BannerTone | undefined;
|
|
32
|
-
title?: string | undefined;
|
|
33
|
-
/** A PrimeIcons class to use instead of the tone's icon; `""` for none. */
|
|
34
|
-
icon?: string | undefined;
|
|
35
|
-
size?: "sm" | "md" | undefined;
|
|
36
|
-
/** Show a close button; it emits `dismiss`. */
|
|
37
|
-
dismissible?: boolean | undefined;
|
|
38
|
-
/** The close button's name. */
|
|
39
|
-
dismissLabel?: string | undefined;
|
|
40
|
-
/** Part of the page, not news: `role="note"` instead of a live region. */
|
|
41
|
-
note?: boolean | undefined;
|
|
42
|
-
}>(), { tone: "info", size: "md", icon: undefined, dismissLabel: "Dismiss" });
|
|
43
|
-
|
|
44
|
-
const emit = defineEmits<{ (e: "dismiss"): void }>();
|
|
45
|
-
const slots = useSlots();
|
|
46
|
-
|
|
47
|
-
const iconClass = computed(() => props.icon ?? TONE_ICON[props.tone]);
|
|
48
|
-
</script>
|
|
49
|
-
|
|
50
|
-
<template>
|
|
51
|
-
<div
|
|
52
|
-
class="wp-banner"
|
|
53
|
-
:data-tone="tone"
|
|
54
|
-
:data-size="size"
|
|
55
|
-
:role="note ? 'note' : tone === 'danger' ? 'alert' : 'status'"
|
|
56
|
-
>
|
|
57
|
-
<i v-if="iconClass" class="pi wp-banner__icon" :class="iconClass" aria-hidden="true" />
|
|
58
|
-
<div class="wp-banner__body">
|
|
59
|
-
<p v-if="title" class="wp-banner__title">{{ title }}</p>
|
|
60
|
-
<div v-if="slots.default" class="wp-banner__text"><slot /></div>
|
|
61
|
-
</div>
|
|
62
|
-
<div v-if="slots.actions" class="wp-banner__actions"><slot name="actions" /></div>
|
|
63
|
-
<CloseButton
|
|
64
|
-
v-if="dismissible"
|
|
65
|
-
class="wp-banner__close"
|
|
66
|
-
size="sm"
|
|
67
|
-
:label="dismissLabel"
|
|
68
|
-
@click="emit('dismiss')"
|
|
69
|
-
/>
|
|
70
|
-
</div>
|
|
71
|
-
</template>
|
|
72
|
-
|
|
73
|
-
<style scoped>
|
|
74
|
-
.wp-banner {
|
|
75
|
-
--_banner-tone: var(--wp-info);
|
|
76
|
-
display: flex;
|
|
77
|
-
align-items: flex-start;
|
|
78
|
-
gap: var(--wp-space-4);
|
|
79
|
-
min-width: 0;
|
|
80
|
-
padding: var(--wp-space-4) var(--wp-space-5);
|
|
81
|
-
border: 1px solid color-mix(in srgb, var(--_banner-tone) 38%, var(--wp-border));
|
|
82
|
-
border-radius: var(--wp-radius-md);
|
|
83
|
-
background: color-mix(in srgb, var(--_banner-tone) 10%, var(--wp-bg-1));
|
|
84
|
-
color: var(--wp-text);
|
|
85
|
-
font: 400 var(--wp-text-sm)/1.45 var(--wp-font-sans);
|
|
86
|
-
}
|
|
87
|
-
.wp-banner[data-tone="neutral"] { --_banner-tone: var(--wp-text-muted); }
|
|
88
|
-
.wp-banner[data-tone="success"] { --_banner-tone: var(--wp-success); }
|
|
89
|
-
.wp-banner[data-tone="warn"] { --_banner-tone: var(--wp-warn); }
|
|
90
|
-
.wp-banner[data-tone="danger"] { --_banner-tone: var(--wp-danger); }
|
|
91
|
-
.wp-banner[data-tone="accent"] { --_banner-tone: var(--wp-accent-500); }
|
|
92
|
-
.wp-banner[data-size="sm"] {
|
|
93
|
-
gap: var(--wp-space-3);
|
|
94
|
-
padding: var(--wp-space-3) var(--wp-space-4);
|
|
95
|
-
border-radius: var(--wp-radius);
|
|
96
|
-
font-size: var(--wp-text-xs);
|
|
97
|
-
}
|
|
98
|
-
.wp-banner__icon {
|
|
99
|
-
flex: none;
|
|
100
|
-
/* Centre the icon on the first line of text. */
|
|
101
|
-
line-height: 1.45;
|
|
102
|
-
font-size: 1.1em;
|
|
103
|
-
color: var(--_banner-tone);
|
|
104
|
-
}
|
|
105
|
-
.wp-banner[data-tone="accent"] .wp-banner__icon { color: var(--wp-accent-text); }
|
|
106
|
-
.wp-banner__body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
|
|
107
|
-
.wp-banner__title { margin: 0; font-weight: 600; }
|
|
108
|
-
.wp-banner__text { color: var(--wp-text-muted); overflow-wrap: anywhere; }
|
|
109
|
-
.wp-banner__body > .wp-banner__text:only-child { color: var(--wp-text); }
|
|
110
|
-
.wp-banner__text :deep(p) { margin: 0; }
|
|
111
|
-
.wp-banner__text :deep(p + p) { margin-top: var(--wp-space-2); }
|
|
112
|
-
.wp-banner__actions { display: inline-flex; align-items: center; gap: var(--wp-space-3); flex: none; align-self: center; }
|
|
113
|
-
.wp-banner__close { flex: none; margin: -2px calc(var(--wp-space-2) * -1) -2px 0; }
|
|
114
|
-
@media (max-width: 520px) {
|
|
115
|
-
.wp-banner { flex-wrap: wrap; }
|
|
116
|
-
.wp-banner__actions { order: 3; width: 100%; padding-left: calc(1.1em + var(--wp-space-4)); }
|
|
117
|
-
}
|
|
118
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Banner: an inline message in a tone, with an optional title, actions and
|
|
4
|
+
* a close button. It sits in the flow of the page (not floating), above or
|
|
5
|
+
* inside whatever it talks about. Works on the canvas and in the manager.
|
|
6
|
+
*
|
|
7
|
+
* <Banner tone="warn" title="2 modules are out of date">
|
|
8
|
+
* Update them before you publish.
|
|
9
|
+
* <template #actions><Button size="sm">Update</Button></template>
|
|
10
|
+
* </Banner>
|
|
11
|
+
*
|
|
12
|
+
* A danger banner is announced at once (`role="alert"`); the others are
|
|
13
|
+
* read politely (`role="status"`). Neither takes focus. `note` is for text
|
|
14
|
+
* that is part of the page (a docs tip): `role="note"`, never announced.
|
|
15
|
+
*/
|
|
16
|
+
import { computed, useSlots } from "vue";
|
|
17
|
+
import CloseButton from "./CloseButton.vue";
|
|
18
|
+
|
|
19
|
+
export type BannerTone = "neutral" | "info" | "success" | "warn" | "danger" | "accent";
|
|
20
|
+
|
|
21
|
+
const TONE_ICON: Record<BannerTone, string> = {
|
|
22
|
+
neutral: "pi-info-circle",
|
|
23
|
+
info: "pi-info-circle",
|
|
24
|
+
success: "pi-check-circle",
|
|
25
|
+
warn: "pi-exclamation-triangle",
|
|
26
|
+
danger: "pi-times-circle",
|
|
27
|
+
accent: "pi-sparkles",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const props = withDefaults(defineProps<{
|
|
31
|
+
tone?: BannerTone | undefined;
|
|
32
|
+
title?: string | undefined;
|
|
33
|
+
/** A PrimeIcons class to use instead of the tone's icon; `""` for none. */
|
|
34
|
+
icon?: string | undefined;
|
|
35
|
+
size?: "sm" | "md" | undefined;
|
|
36
|
+
/** Show a close button; it emits `dismiss`. */
|
|
37
|
+
dismissible?: boolean | undefined;
|
|
38
|
+
/** The close button's name. */
|
|
39
|
+
dismissLabel?: string | undefined;
|
|
40
|
+
/** Part of the page, not news: `role="note"` instead of a live region. */
|
|
41
|
+
note?: boolean | undefined;
|
|
42
|
+
}>(), { tone: "info", size: "md", icon: undefined, dismissLabel: "Dismiss" });
|
|
43
|
+
|
|
44
|
+
const emit = defineEmits<{ (e: "dismiss"): void }>();
|
|
45
|
+
const slots = useSlots();
|
|
46
|
+
|
|
47
|
+
const iconClass = computed(() => props.icon ?? TONE_ICON[props.tone]);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<div
|
|
52
|
+
class="wp-banner"
|
|
53
|
+
:data-tone="tone"
|
|
54
|
+
:data-size="size"
|
|
55
|
+
:role="note ? 'note' : tone === 'danger' ? 'alert' : 'status'"
|
|
56
|
+
>
|
|
57
|
+
<i v-if="iconClass" class="pi wp-banner__icon" :class="iconClass" aria-hidden="true" />
|
|
58
|
+
<div class="wp-banner__body">
|
|
59
|
+
<p v-if="title" class="wp-banner__title">{{ title }}</p>
|
|
60
|
+
<div v-if="slots.default" class="wp-banner__text"><slot /></div>
|
|
61
|
+
</div>
|
|
62
|
+
<div v-if="slots.actions" class="wp-banner__actions"><slot name="actions" /></div>
|
|
63
|
+
<CloseButton
|
|
64
|
+
v-if="dismissible"
|
|
65
|
+
class="wp-banner__close"
|
|
66
|
+
size="sm"
|
|
67
|
+
:label="dismissLabel"
|
|
68
|
+
@click="emit('dismiss')"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<style scoped>
|
|
74
|
+
.wp-banner {
|
|
75
|
+
--_banner-tone: var(--wp-info);
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: flex-start;
|
|
78
|
+
gap: var(--wp-space-4);
|
|
79
|
+
min-width: 0;
|
|
80
|
+
padding: var(--wp-space-4) var(--wp-space-5);
|
|
81
|
+
border: 1px solid color-mix(in srgb, var(--_banner-tone) 38%, var(--wp-border));
|
|
82
|
+
border-radius: var(--wp-radius-md);
|
|
83
|
+
background: color-mix(in srgb, var(--_banner-tone) 10%, var(--wp-bg-1));
|
|
84
|
+
color: var(--wp-text);
|
|
85
|
+
font: 400 var(--wp-text-sm)/1.45 var(--wp-font-sans);
|
|
86
|
+
}
|
|
87
|
+
.wp-banner[data-tone="neutral"] { --_banner-tone: var(--wp-text-muted); }
|
|
88
|
+
.wp-banner[data-tone="success"] { --_banner-tone: var(--wp-success); }
|
|
89
|
+
.wp-banner[data-tone="warn"] { --_banner-tone: var(--wp-warn); }
|
|
90
|
+
.wp-banner[data-tone="danger"] { --_banner-tone: var(--wp-danger); }
|
|
91
|
+
.wp-banner[data-tone="accent"] { --_banner-tone: var(--wp-accent-500); }
|
|
92
|
+
.wp-banner[data-size="sm"] {
|
|
93
|
+
gap: var(--wp-space-3);
|
|
94
|
+
padding: var(--wp-space-3) var(--wp-space-4);
|
|
95
|
+
border-radius: var(--wp-radius);
|
|
96
|
+
font-size: var(--wp-text-xs);
|
|
97
|
+
}
|
|
98
|
+
.wp-banner__icon {
|
|
99
|
+
flex: none;
|
|
100
|
+
/* Centre the icon on the first line of text. */
|
|
101
|
+
line-height: 1.45;
|
|
102
|
+
font-size: 1.1em;
|
|
103
|
+
color: var(--_banner-tone);
|
|
104
|
+
}
|
|
105
|
+
.wp-banner[data-tone="accent"] .wp-banner__icon { color: var(--wp-accent-text); }
|
|
106
|
+
.wp-banner__body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
|
|
107
|
+
.wp-banner__title { margin: 0; font-weight: 600; }
|
|
108
|
+
.wp-banner__text { color: var(--wp-text-muted); overflow-wrap: anywhere; }
|
|
109
|
+
.wp-banner__body > .wp-banner__text:only-child { color: var(--wp-text); }
|
|
110
|
+
.wp-banner__text :deep(p) { margin: 0; }
|
|
111
|
+
.wp-banner__text :deep(p + p) { margin-top: var(--wp-space-2); }
|
|
112
|
+
.wp-banner__actions { display: inline-flex; align-items: center; gap: var(--wp-space-3); flex: none; align-self: center; }
|
|
113
|
+
.wp-banner__close { flex: none; margin: -2px calc(var(--wp-space-2) * -1) -2px 0; }
|
|
114
|
+
@media (max-width: 520px) {
|
|
115
|
+
.wp-banner { flex-wrap: wrap; }
|
|
116
|
+
.wp-banner__actions { order: 3; width: 100%; padding-left: calc(1.1em + var(--wp-space-4)); }
|
|
117
|
+
}
|
|
118
|
+
</style>
|
package/src/BookmarkButton.vue
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Bookmark a post: a toggle Button with a filled bookmark when on. The app
|
|
4
|
-
* owns the request; this only shows the state and emits `toggle`.
|
|
5
|
-
*
|
|
6
|
-
* <BookmarkButton :bookmarked="post.bookmarked_by_me" :disabled="pending" @toggle="bookmark" />
|
|
7
|
-
*/
|
|
8
|
-
import Button from "./Button.vue";
|
|
9
|
-
|
|
10
|
-
withDefaults(defineProps<{
|
|
11
|
-
bookmarked: boolean;
|
|
12
|
-
disabled?: boolean | undefined;
|
|
13
|
-
size?: "sm" | "md" | "lg" | undefined;
|
|
14
|
-
}>(), { disabled: false, size: "md" });
|
|
15
|
-
|
|
16
|
-
const emit = defineEmits<{ (e: "toggle"): void }>();
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<template>
|
|
20
|
-
<Button
|
|
21
|
-
:icon="bookmarked ? 'bookmark-fill' : 'bookmark'"
|
|
22
|
-
:pressed="bookmarked"
|
|
23
|
-
:disabled="disabled"
|
|
24
|
-
:size="size"
|
|
25
|
-
@click="emit('toggle')"
|
|
26
|
-
>{{ bookmarked ? "Bookmarked" : "Bookmark" }}</Button>
|
|
27
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Bookmark a post: a toggle Button with a filled bookmark when on. The app
|
|
4
|
+
* owns the request; this only shows the state and emits `toggle`.
|
|
5
|
+
*
|
|
6
|
+
* <BookmarkButton :bookmarked="post.bookmarked_by_me" :disabled="pending" @toggle="bookmark" />
|
|
7
|
+
*/
|
|
8
|
+
import Button from "./Button.vue";
|
|
9
|
+
|
|
10
|
+
withDefaults(defineProps<{
|
|
11
|
+
bookmarked: boolean;
|
|
12
|
+
disabled?: boolean | undefined;
|
|
13
|
+
size?: "sm" | "md" | "lg" | undefined;
|
|
14
|
+
}>(), { disabled: false, size: "md" });
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits<{ (e: "toggle"): void }>();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<Button
|
|
21
|
+
:icon="bookmarked ? 'bookmark-fill' : 'bookmark'"
|
|
22
|
+
:pressed="bookmarked"
|
|
23
|
+
:disabled="disabled"
|
|
24
|
+
:size="size"
|
|
25
|
+
@click="emit('toggle')"
|
|
26
|
+
>{{ bookmarked ? "Bookmarked" : "Bookmark" }}</Button>
|
|
27
|
+
</template>
|
package/src/BundleMeta.vue
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* One line saying what a bundle holds: a box mark, "3 modules" and their
|
|
4
|
-
* names joined by middots.
|
|
5
|
-
*
|
|
6
|
-
* <BundleMeta :count="3" :names="['palette', 'mood', 'style']" />
|
|
7
|
-
*/
|
|
8
|
-
import Icon from "./Icon.vue";
|
|
9
|
-
|
|
10
|
-
defineProps<{
|
|
11
|
-
count: number;
|
|
12
|
-
names?: string[] | undefined;
|
|
13
|
-
}>();
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<template>
|
|
17
|
-
<div class="wp-bundle-meta">
|
|
18
|
-
<span class="wp-bundle-meta__icon" aria-hidden="true"><Icon name="box" :size="10" /></span>
|
|
19
|
-
<span class="wp-bundle-meta__text">
|
|
20
|
-
<span class="wp-bundle-meta__count">{{ count }} {{ count === 1 ? "module" : "modules" }}</span>
|
|
21
|
-
<template v-for="(n, i) in names ?? []" :key="i">
|
|
22
|
-
<span class="wp-bundle-meta__sep">·</span>{{ n }}
|
|
23
|
-
</template>
|
|
24
|
-
</span>
|
|
25
|
-
</div>
|
|
26
|
-
</template>
|
|
27
|
-
|
|
28
|
-
<style scoped>
|
|
29
|
-
.wp-bundle-meta {
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: flex-start;
|
|
32
|
-
gap: 8px;
|
|
33
|
-
font-family: var(--wp-font-mono);
|
|
34
|
-
font-size: 11.5px;
|
|
35
|
-
line-height: 1.5;
|
|
36
|
-
color: var(--wp-text-dim);
|
|
37
|
-
}
|
|
38
|
-
.wp-bundle-meta__icon {
|
|
39
|
-
display: inline-grid;
|
|
40
|
-
place-items: center;
|
|
41
|
-
flex-shrink: 0;
|
|
42
|
-
width: 18px;
|
|
43
|
-
height: 18px;
|
|
44
|
-
border-radius: min(4px, var(--wp-radius-cap, 999px));
|
|
45
|
-
background: color-mix(in oklab, var(--wp-bundle-default) 18%, transparent);
|
|
46
|
-
color: var(--wp-text-muted);
|
|
47
|
-
}
|
|
48
|
-
.wp-bundle-meta__text { flex: 1; min-width: 0; word-break: break-word; }
|
|
49
|
-
.wp-bundle-meta__count { color: var(--wp-text); font-weight: 500; }
|
|
50
|
-
.wp-bundle-meta__sep { margin: 0 4px; color: var(--wp-text-dim); }
|
|
51
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* One line saying what a bundle holds: a box mark, "3 modules" and their
|
|
4
|
+
* names joined by middots.
|
|
5
|
+
*
|
|
6
|
+
* <BundleMeta :count="3" :names="['palette', 'mood', 'style']" />
|
|
7
|
+
*/
|
|
8
|
+
import Icon from "./Icon.vue";
|
|
9
|
+
|
|
10
|
+
defineProps<{
|
|
11
|
+
count: number;
|
|
12
|
+
names?: string[] | undefined;
|
|
13
|
+
}>();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="wp-bundle-meta">
|
|
18
|
+
<span class="wp-bundle-meta__icon" aria-hidden="true"><Icon name="box" :size="10" /></span>
|
|
19
|
+
<span class="wp-bundle-meta__text">
|
|
20
|
+
<span class="wp-bundle-meta__count">{{ count }} {{ count === 1 ? "module" : "modules" }}</span>
|
|
21
|
+
<template v-for="(n, i) in names ?? []" :key="i">
|
|
22
|
+
<span class="wp-bundle-meta__sep">·</span>{{ n }}
|
|
23
|
+
</template>
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<style scoped>
|
|
29
|
+
.wp-bundle-meta {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: flex-start;
|
|
32
|
+
gap: 8px;
|
|
33
|
+
font-family: var(--wp-font-mono);
|
|
34
|
+
font-size: 11.5px;
|
|
35
|
+
line-height: 1.5;
|
|
36
|
+
color: var(--wp-text-dim);
|
|
37
|
+
}
|
|
38
|
+
.wp-bundle-meta__icon {
|
|
39
|
+
display: inline-grid;
|
|
40
|
+
place-items: center;
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
width: 18px;
|
|
43
|
+
height: 18px;
|
|
44
|
+
border-radius: min(4px, var(--wp-radius-cap, 999px));
|
|
45
|
+
background: color-mix(in oklab, var(--wp-bundle-default) 18%, transparent);
|
|
46
|
+
color: var(--wp-text-muted);
|
|
47
|
+
}
|
|
48
|
+
.wp-bundle-meta__text { flex: 1; min-width: 0; word-break: break-word; }
|
|
49
|
+
.wp-bundle-meta__count { color: var(--wp-text); font-weight: 500; }
|
|
50
|
+
.wp-bundle-meta__sep { margin: 0 4px; color: var(--wp-text-dim); }
|
|
51
|
+
</style>
|