@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +92 -51
  2. package/LICENSE +674 -674
  3. package/README.md +73 -76
  4. package/package.json +78 -77
  5. package/src/Avatar.vue +83 -83
  6. package/src/Badge.vue +97 -97
  7. package/src/Banner.vue +118 -118
  8. package/src/BookmarkButton.vue +27 -27
  9. package/src/BundleMeta.vue +51 -51
  10. package/src/Button.vue +103 -103
  11. package/src/Card.vue +68 -68
  12. package/src/Checkbox.vue +109 -107
  13. package/src/Chip.vue +163 -97
  14. package/src/ChoiceCards.vue +133 -133
  15. package/src/CloseButton.vue +69 -69
  16. package/src/CodeBlock.vue +146 -119
  17. package/src/ColorField.vue +188 -206
  18. package/src/ColorPicker.vue +201 -201
  19. package/src/Combobox.vue +331 -334
  20. package/src/ConfirmDialog.vue +165 -163
  21. package/src/ConfirmModal.vue +147 -147
  22. package/src/ConstraintMatrixPreview.vue +708 -708
  23. package/src/ContextMenu.vue +386 -379
  24. package/src/CopyButton.vue +156 -156
  25. package/src/DataTable.vue +293 -292
  26. package/src/DatePicker.vue +234 -234
  27. package/src/DescriptionList.vue +73 -73
  28. package/src/DiffView.vue +68 -68
  29. package/src/Disclosure.vue +166 -166
  30. package/src/DragHandle.vue +49 -49
  31. package/src/Drawer.vue +131 -131
  32. package/src/DropZone.vue +196 -196
  33. package/src/DropdownMenu.vue +72 -72
  34. package/src/EmptyState.vue +111 -111
  35. package/src/Field.vue +28 -28
  36. package/src/HsvPicker.vue +208 -208
  37. package/src/Icon.vue +39 -39
  38. package/src/IconPicker.vue +209 -209
  39. package/src/ImageGrid.vue +127 -125
  40. package/src/InlineEdit.vue +183 -183
  41. package/src/Input.vue +99 -99
  42. package/src/Kbd.vue +56 -56
  43. package/src/Lightbox.vue +176 -173
  44. package/src/ListFilter.vue +85 -85
  45. package/src/ListRow.vue +204 -204
  46. package/src/LoadingMark.vue +217 -161
  47. package/src/Logo.vue +60 -60
  48. package/src/Modal.vue +78 -76
  49. package/src/ModalShell.vue +109 -107
  50. package/src/Pagination.vue +106 -106
  51. package/src/Popover.vue +180 -171
  52. package/src/PostCard.vue +210 -211
  53. package/src/ProgressBar.vue +90 -90
  54. package/src/RadioGroup.vue +177 -177
  55. package/src/RangeSlider.vue +183 -183
  56. package/src/RelativeDate.vue +35 -35
  57. package/src/ResizeHandle.vue +143 -143
  58. package/src/SearchInput.vue +192 -199
  59. package/src/Select.vue +644 -641
  60. package/src/Separator.vue +54 -54
  61. package/src/Skeleton.vue +98 -82
  62. package/src/Slider.vue +198 -198
  63. package/src/SortableList.vue +260 -260
  64. package/src/SplitPane.vue +68 -68
  65. package/src/StarButton.vue +40 -40
  66. package/src/Stepper.vue +141 -141
  67. package/src/SupportLinks.vue +93 -93
  68. package/src/TagInput.vue +297 -300
  69. package/src/Textarea.vue +57 -57
  70. package/src/Timeline.vue +92 -92
  71. package/src/ToastCard.vue +119 -119
  72. package/src/ToastHost.vue +40 -40
  73. package/src/Toggle.vue +36 -36
  74. package/src/TokenizedValue.vue +310 -310
  75. package/src/Toolbar.vue +110 -110
  76. package/src/VirtualList.vue +87 -87
  77. package/src/VisuallyHidden.vue +29 -29
  78. package/src/WpButton.vue +75 -75
  79. package/src/WpCheck.vue +104 -102
  80. package/src/WpIconButton.vue +107 -107
  81. package/src/WpImageZoom.vue +544 -544
  82. package/src/WpNumber.vue +178 -183
  83. package/src/WpSegmented.vue +175 -175
  84. package/src/WpSelect.vue +93 -89
  85. package/src/WpSwitch.vue +163 -163
  86. package/src/WpTabs.vue +188 -188
  87. package/src/_modal-motion.css +33 -33
  88. package/src/_skeleton.css +50 -50
  89. package/src/axis-color.ts +42 -42
  90. package/src/button.css +169 -169
  91. package/src/calendar.ts +59 -59
  92. package/src/clipboard.ts +50 -50
  93. package/src/colour-presets.ts +12 -12
  94. package/src/diff.ts +51 -51
  95. package/src/drop-zone.ts +41 -41
  96. package/src/field.css +50 -0
  97. package/src/flip.ts +237 -237
  98. package/src/floating-layer.ts +48 -0
  99. package/src/floating.ts +99 -99
  100. package/src/highlight.ts +199 -0
  101. package/src/hsv.ts +100 -100
  102. package/src/icon-catalog.ts +52 -52
  103. package/src/image-zoom.ts +93 -93
  104. package/src/kind-colour.ts +28 -0
  105. package/src/loading-mark-motion.ts +249 -0
  106. package/src/logo-paths.ts +21 -21
  107. package/src/matrix-axis.ts +91 -91
  108. package/src/pagination.ts +27 -27
  109. package/src/platform-keys.ts +29 -29
  110. package/src/rich-tokenize.ts +357 -357
  111. package/src/select-types.ts +32 -32
  112. package/src/slider-math.ts +45 -45
  113. package/src/sortable.ts +22 -22
  114. package/src/theme/fonts/OFL-Fraunces.txt +93 -93
  115. package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -93
  116. package/src/theme/fonts/OFL-Manrope.txt +93 -93
  117. package/src/theme/looks.css +428 -428
  118. package/src/theme/palettes.css +254 -254
  119. package/src/theme/tokens.css +2896 -2894
  120. package/src/tokens.ts +56 -56
  121. package/src/tooltip.css +43 -43
  122. package/src/tooltip.ts +146 -146
  123. package/src/useDialogFocus.ts +73 -73
  124. package/src/useGrowableField.ts +357 -357
  125. package/src/useModalKeyShield.ts +116 -116
  126. package/src/useToast.ts +48 -48
  127. package/src/wp-logo.svg +14 -14
  128. package/src/ChipToggle.vue +0 -92
  129. package/src/KindChip.vue +0 -53
  130. 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>
@@ -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>
@@ -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>