@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
@@ -1,161 +1,217 @@
1
- <script lang="ts">
2
- /** Gradient ids must be unique on the page. `useId` restarts per Vue app and
3
- * the extension mounts many small apps, so count across all of them. */
4
- let nextId = 0;
5
- </script>
6
-
7
- <script setup lang="ts">
8
- /**
9
- * The loading mark. Two drawings, one per kind of place:
10
- *
11
- * - `logo` (pages and panels): the full logo. Its two rings slide apart and
12
- * back while the big star dips and turns and the small stars twinkle. A
13
- * `block` mark, or one 24 px or bigger, gets it by default.
14
- * - `sparkle` (tight inline spots: a text row, a button, a list item): the
15
- * logo's three stars without the rings, the big one dipping while the small
16
- * ones swap places, like the AI working line. Small marks get it by
17
- * default, so an inline "Loading bundle…" is not a big colourful logo.
18
- *
19
- * Alone it is an icon (`size`, 14 inline and 40 as a block). With slot text it
20
- * becomes a row ("[mark] Loading bundle…"); with `block` it is centred in its
21
- * box, for a panel or page that is loading. `mono` draws it in the text
22
- * colour, for a host with its own colours (a busy button).
23
- */
24
- import { computed, useSlots } from "vue";
25
- import { BRAND_COLOURS, RING_A, RING_B, STAR_A, STAR_B, STAR_BIG, type LogoBrand } from "./logo-paths";
26
-
27
- const props = withDefaults(defineProps<{
28
- /** Width and height in px. Default 14, or 40 with `block`. */
29
- size?: number | undefined;
30
- /** Accessible name when there is no slot text. */
31
- label?: string | undefined;
32
- /** Centre it (and its text, below) in the space it is given. */
33
- block?: boolean | undefined;
34
- /** Which drawing; default `logo` for a block or a 24 px+ mark, else `sparkle`. */
35
- variant?: "logo" | "sparkle" | undefined;
36
- /** Draw in the current text colour instead of the logo colours. */
37
- mono?: boolean | undefined;
38
- /** Hidden from screen readers, for a host that says it is busy itself
39
- * (a button with `aria-busy`). */
40
- decorative?: boolean | undefined;
41
- /** Whose colours: the extension's (default) or the community site's. */
42
- brand?: LogoBrand | undefined;
43
- }>(), { size: undefined, label: "Loading", block: false, variant: undefined, mono: false, decorative: false, brand: "extension" });
44
-
45
- const slots = useSlots();
46
- const id = `wp-lm-${nextId++}`;
47
- const px = computed(() => props.size ?? (props.block ? 40 : 14));
48
- const drawing = computed(() => props.variant ?? (props.block || px.value >= 24 ? "logo" : "sparkle"));
49
- /** The logo's own colours (brand, not theme tokens: the mark looks the same
50
- * in light and dark). */
51
- const VIOLET = computed(() => BRAND_COLOURS[props.brand][0]);
52
- const INDIGO = computed(() => BRAND_COLOURS[props.brand][1]);
53
- const TEAL = computed(() => BRAND_COLOURS[props.brand][2]);
54
- const fill = (colour: string): string => (props.mono ? "currentColor" : colour);
55
- </script>
56
-
57
- <template>
58
- <span
59
- class="wp-lm"
60
- :class="{ 'wp-lm--block': block }"
61
- :role="decorative ? undefined : 'status'"
62
- :aria-label="decorative || slots.default ? undefined : label"
63
- :aria-hidden="decorative || undefined"
64
- data-test="loading-mark"
65
- >
66
- <svg
67
- class="wp-lm__svg"
68
- :data-variant="drawing"
69
- viewBox="0 0 1024 1024"
70
- :width="px"
71
- :height="px"
72
- aria-hidden="true"
73
- >
74
- <template v-if="drawing === 'logo'">
75
- <defs v-if="!mono">
76
- <linearGradient :id="`${id}a`" x1="312.56" y1="897.19" x2="312.56" y2="2" gradientUnits="userSpaceOnUse">
77
- <stop offset="0" :stop-color="VIOLET" />
78
- <stop offset="1" :stop-color="INDIGO" />
79
- </linearGradient>
80
- <linearGradient :id="`${id}b`" x1="711.44" y1="126.81" x2="711.44" y2="1022" gradientUnits="userSpaceOnUse">
81
- <stop offset="0" :stop-color="INDIGO" />
82
- <stop offset="1" :stop-color="TEAL" />
83
- </linearGradient>
84
- </defs>
85
- <path class="wp-lm__ring wp-lm__ring--a" :fill="fill(`url(#${id}a)`)" :d="RING_A" />
86
- <path class="wp-lm__ring wp-lm__ring--b" :fill="fill(`url(#${id}b)`)" :d="RING_B" />
87
- <path class="wp-lm__star wp-lm__star--big" :fill="fill(VIOLET)" :d="STAR_BIG" />
88
- <path class="wp-lm__star wp-lm__star--tw" :fill="fill(INDIGO)" :d="STAR_A" />
89
- <path class="wp-lm__star wp-lm__star--tw" :fill="fill(TEAL)" :d="STAR_B" />
90
- </template>
91
- <g v-else transform="translate(512 512) scale(1.45) translate(-512 -512)">
92
- <path class="wp-lm__star wp-lm__star--big" :fill="fill(VIOLET)" :d="STAR_BIG" />
93
- <g class="wp-lm__swap">
94
- <path class="wp-lm__star" :fill="fill(INDIGO)" :d="STAR_A" />
95
- <path class="wp-lm__star" :fill="fill(TEAL)" :d="STAR_B" />
96
- </g>
97
- </g>
98
- </svg>
99
- <span v-if="slots.default" class="wp-lm__text"><slot /></span>
100
- </span>
101
- </template>
102
-
103
- <style scoped>
104
- .wp-lm {
105
- --wp-lm-t: 2.4s;
106
- display: inline-flex;
107
- align-items: center;
108
- gap: 8px;
109
- vertical-align: middle;
110
- }
111
- .wp-lm--block {
112
- display: flex;
113
- flex-direction: column;
114
- justify-content: center;
115
- gap: 12px;
116
- width: 100%;
117
- padding: 48px 16px;
118
- color: var(--wp-text-muted);
119
- text-align: center;
120
- }
121
- .wp-lm__svg { flex-shrink: 0; overflow: visible; }
122
- .wp-lm__ring { transform-box: view-box; transform-origin: 512px 512px; }
123
- .wp-lm__star { transform-box: fill-box; transform-origin: center; }
124
- .wp-lm__ring--a { animation: wp-lm-split-a var(--wp-lm-t) cubic-bezier(0.65, 0, 0.35, 1) infinite; }
125
- .wp-lm__ring--b { animation: wp-lm-split-b var(--wp-lm-t) cubic-bezier(0.65, 0, 0.35, 1) infinite; }
126
- .wp-lm__star--big { animation: wp-lm-dip var(--wp-lm-t) cubic-bezier(0.65, 0, 0.35, 1) infinite; }
127
- .wp-lm__star--tw { animation: wp-lm-twinkle var(--wp-lm-t) ease-in-out infinite; }
128
- .wp-lm__swap {
129
- transform-box: view-box; transform-origin: 512px 512px;
130
- animation: wp-lm-swap calc(var(--wp-lm-t) * 2) cubic-bezier(0.65, 0, 0.35, 1) infinite;
131
- }
132
-
133
- @keyframes wp-lm-split-a {
134
- 0%, 15%, 85%, 100% { transform: translate(0, 0); }
135
- 40%, 60% { transform: translate(-70px, -70px); }
136
- }
137
- @keyframes wp-lm-split-b {
138
- 0%, 15%, 85%, 100% { transform: translate(0, 0); }
139
- 40%, 60% { transform: translate(70px, 70px); }
140
- }
141
- @keyframes wp-lm-dip {
142
- 0%, 15% { transform: scale(1) rotate(0deg); }
143
- 32% { transform: scale(0.7) rotate(45deg); }
144
- 50%, 100% { transform: scale(1) rotate(90deg); }
145
- }
146
- @keyframes wp-lm-swap {
147
- 0%, 15% { transform: rotate(0deg); }
148
- 50%, 65% { transform: rotate(180deg); }
149
- 100% { transform: rotate(360deg); }
150
- }
151
- @keyframes wp-lm-twinkle {
152
- 0%, 100% { opacity: 1; transform: scale(1); }
153
- 50% { opacity: 0.35; transform: scale(0.6); }
154
- }
155
-
156
- @media (prefers-reduced-motion: reduce) {
157
- .wp-lm__svg * { animation: none !important; }
158
- .wp-lm__svg { animation: wp-lm-fade 2.4s ease-in-out infinite; }
159
- @keyframes wp-lm-fade { 50% { opacity: 0.55; } }
160
- }
161
- </style>
1
+ <script lang="ts">
2
+ /** Gradient ids must be unique on the page. `useId` restarts per Vue app and
3
+ * the extension mounts many small apps, so count across all of them. */
4
+ let nextId = 0;
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ /**
9
+ * The loading mark. Two drawings, one per kind of place:
10
+ *
11
+ * - `logo` (pages and panels): the full logo. Its two rings slide apart and
12
+ * back while the big star dips and turns and the small stars twinkle. A
13
+ * `block` mark, or one 24 px or bigger, gets it by default.
14
+ * - `sparkle` (tight inline spots: a text row, a button, a list item): the
15
+ * logo's three stars without the rings, the big one dipping while the small
16
+ * ones swap places, like the AI working line. Small marks get it by
17
+ * default, so an inline "Loading bundle…" is not a big colourful logo.
18
+ *
19
+ * Alone it is an icon (`size`, 14 inline and 40 as a block). With slot text it
20
+ * becomes a row ("[mark] Loading bundle…"); with `block` it is centred in its
21
+ * box, for a panel or page that is loading. `mono` draws it in the text
22
+ * colour, for a host with its own colours (a busy button).
23
+ */
24
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, useSlots } from "vue";
25
+ import { BRAND_COLOURS, RING_A, RING_B, STAR_A, STAR_B, STAR_BIG, type LogoBrand } from "./logo-paths";
26
+ import { BOX, PAD, animate, drawNow, type Animated, type Look } from "./loading-mark-motion";
27
+
28
+ const props = withDefaults(defineProps<{
29
+ /** Width and height in px. Default 14, or 40 with `block`. */
30
+ size?: number | undefined;
31
+ /** Accessible name when there is no slot text. */
32
+ label?: string | undefined;
33
+ /** Centre it (and its text, below) in the space it is given. */
34
+ block?: boolean | undefined;
35
+ /** Which drawing; default `logo` for a block or a 24 px+ mark, else `sparkle`. */
36
+ variant?: "logo" | "sparkle" | undefined;
37
+ /** Draw in the current text colour instead of the logo colours. */
38
+ mono?: boolean | undefined;
39
+ /** Hidden from screen readers, for a host that says it is busy itself
40
+ * (a button with `aria-busy`). */
41
+ decorative?: boolean | undefined;
42
+ /** Whose colours: the extension's (default) or the community site's. */
43
+ brand?: LogoBrand | undefined;
44
+ }>(), { size: undefined, label: "Loading", block: false, variant: undefined, mono: false, decorative: false, brand: "extension" });
45
+
46
+ const slots = useSlots();
47
+ const id = `wp-lm-${nextId++}`;
48
+ const px = computed(() => props.size ?? (props.block ? 40 : 14));
49
+ const drawing = computed(() => props.variant ?? (props.block || px.value >= 24 ? "logo" : "sparkle"));
50
+ /** The logo's own colours (brand, not theme tokens: the mark looks the same
51
+ * in light and dark). */
52
+ const VIOLET = computed(() => BRAND_COLOURS[props.brand][0]);
53
+ const INDIGO = computed(() => BRAND_COLOURS[props.brand][1]);
54
+ const TEAL = computed(() => BRAND_COLOURS[props.brand][2]);
55
+ const fill = (colour: string): string => (props.mono ? "currentColor" : colour);
56
+
57
+ /* The SVG is the still mark (first paint, reduced motion, a page with no
58
+ * canvas). Once mounted, a canvas over it draws the motion: see
59
+ * `loading-mark-motion.ts` for why it is not CSS. */
60
+ const root = ref<HTMLElement | null>(null);
61
+ const canvas = ref<HTMLCanvasElement | null>(null);
62
+ const live = ref(false);
63
+ const drawn = ref(false);
64
+ const canvasStyle = computed(() => {
65
+ const side = (px.value * BOX) / 1024;
66
+ const off = (px.value * PAD) / 1024;
67
+ return { width: `${side}px`, height: `${side}px`, top: `${-off}px`, left: `${-off}px` };
68
+ });
69
+
70
+ let textColour = "";
71
+ let textColourAt = -Infinity;
72
+ function look(): Look {
73
+ if (props.mono) {
74
+ const now = performance.now();
75
+ if (now - textColourAt > 500 && root.value) {
76
+ textColour = getComputedStyle(root.value).color || "currentColor";
77
+ textColourAt = now;
78
+ }
79
+ return { variant: drawing.value, colours: [textColour, textColour, textColour], flat: true };
80
+ }
81
+ return { variant: drawing.value, colours: [VIOLET.value, INDIGO.value, TEAL.value], flat: false };
82
+ }
83
+
84
+ let stop: (() => void) | null = null;
85
+ let observer: IntersectionObserver | null = null;
86
+ let motion: MediaQueryList | null = null;
87
+
88
+ async function start(): Promise<void> {
89
+ if (stop) return;
90
+ live.value = true;
91
+ await nextTick();
92
+ const el = canvas.value;
93
+ if (!el || !live.value) return;
94
+ const mark: Animated = { canvas: el, look, clock: 0, visible: true };
95
+ if (!drawNow(mark)) {
96
+ live.value = false;
97
+ return;
98
+ }
99
+ drawn.value = true;
100
+ stop = animate(mark);
101
+ if (typeof IntersectionObserver === "function") {
102
+ observer = new IntersectionObserver((entries) => {
103
+ for (const e of entries) mark.visible = e.isIntersecting;
104
+ });
105
+ observer.observe(el);
106
+ }
107
+ }
108
+ function halt(): void {
109
+ stop?.();
110
+ stop = null;
111
+ observer?.disconnect();
112
+ observer = null;
113
+ live.value = false;
114
+ drawn.value = false;
115
+ }
116
+ function onMotionChange(): void {
117
+ if (motion?.matches) halt();
118
+ else void start();
119
+ }
120
+
121
+ onMounted(() => {
122
+ // No matchMedia means no way to honour reduced motion, and a test DOM
123
+ // (jsdom) has no canvas and logs an error for every attempt: keep the
124
+ // still SVG in both.
125
+ if (typeof window.matchMedia !== "function" || typeof requestAnimationFrame !== "function") return;
126
+ if (navigator.userAgent.includes("jsdom")) return;
127
+ motion = window.matchMedia("(prefers-reduced-motion: reduce)");
128
+ motion.addEventListener?.("change", onMotionChange);
129
+ if (!motion.matches) void start();
130
+ });
131
+ onBeforeUnmount(() => {
132
+ motion?.removeEventListener?.("change", onMotionChange);
133
+ halt();
134
+ });
135
+ </script>
136
+
137
+ <template>
138
+ <span
139
+ ref="root"
140
+ class="wp-lm"
141
+ :class="{ 'wp-lm--block': block, 'wp-lm--drawn': drawn }"
142
+ :role="decorative ? undefined : 'status'"
143
+ :aria-label="decorative || slots.default ? undefined : label"
144
+ :aria-hidden="decorative || undefined"
145
+ data-test="loading-mark"
146
+ >
147
+ <span class="wp-lm__art" :style="{ width: `${px}px`, height: `${px}px` }">
148
+ <svg
149
+ class="wp-lm__svg"
150
+ :data-variant="drawing"
151
+ viewBox="0 0 1024 1024"
152
+ :width="px"
153
+ :height="px"
154
+ aria-hidden="true"
155
+ >
156
+ <template v-if="drawing === 'logo'">
157
+ <defs v-if="!mono">
158
+ <linearGradient :id="`${id}a`" x1="312.56" y1="897.19" x2="312.56" y2="2" gradientUnits="userSpaceOnUse">
159
+ <stop offset="0" :stop-color="VIOLET" />
160
+ <stop offset="1" :stop-color="INDIGO" />
161
+ </linearGradient>
162
+ <linearGradient :id="`${id}b`" x1="711.44" y1="126.81" x2="711.44" y2="1022" gradientUnits="userSpaceOnUse">
163
+ <stop offset="0" :stop-color="INDIGO" />
164
+ <stop offset="1" :stop-color="TEAL" />
165
+ </linearGradient>
166
+ </defs>
167
+ <path class="wp-lm__ring wp-lm__ring--a" :fill="fill(`url(#${id}a)`)" :d="RING_A" />
168
+ <path class="wp-lm__ring wp-lm__ring--b" :fill="fill(`url(#${id}b)`)" :d="RING_B" />
169
+ <path class="wp-lm__star wp-lm__star--big" :fill="fill(VIOLET)" :d="STAR_BIG" />
170
+ <path class="wp-lm__star wp-lm__star--tw" :fill="fill(INDIGO)" :d="STAR_A" />
171
+ <path class="wp-lm__star wp-lm__star--tw" :fill="fill(TEAL)" :d="STAR_B" />
172
+ </template>
173
+ <g v-else transform="translate(512 512) scale(1.45) translate(-512 -512)">
174
+ <path class="wp-lm__star wp-lm__star--big" :fill="fill(VIOLET)" :d="STAR_BIG" />
175
+ <g class="wp-lm__swap">
176
+ <path class="wp-lm__star" :fill="fill(INDIGO)" :d="STAR_A" />
177
+ <path class="wp-lm__star" :fill="fill(TEAL)" :d="STAR_B" />
178
+ </g>
179
+ </g>
180
+ </svg>
181
+ <canvas v-if="live" ref="canvas" class="wp-lm__canvas" :style="canvasStyle" aria-hidden="true" />
182
+ </span>
183
+ <span v-if="slots.default" class="wp-lm__text"><slot /></span>
184
+ </span>
185
+ </template>
186
+
187
+ <style scoped>
188
+ .wp-lm {
189
+ display: inline-flex;
190
+ align-items: center;
191
+ gap: 8px;
192
+ vertical-align: middle;
193
+ }
194
+ .wp-lm--block {
195
+ display: flex;
196
+ flex-direction: column;
197
+ justify-content: center;
198
+ gap: 12px;
199
+ width: 100%;
200
+ padding: 48px 16px;
201
+ color: var(--wp-text-muted);
202
+ text-align: center;
203
+ }
204
+ .wp-lm__art { position: relative; display: inline-block; flex-shrink: 0; line-height: 0; }
205
+ .wp-lm__svg { display: block; overflow: visible; }
206
+ .wp-lm__canvas { position: absolute; pointer-events: none; }
207
+ .wp-lm--drawn .wp-lm__svg { visibility: hidden; }
208
+
209
+ /* Reduced motion: no canvas, the still mark breathes. A registered custom
210
+ * property, not `opacity` itself, so the fade stays off the compositor (a
211
+ * composited fade turns the mark soft). */
212
+ @property --wp-lm-o { syntax: "<number>"; inherits: false; initial-value: 1; }
213
+ @media (prefers-reduced-motion: reduce) {
214
+ .wp-lm__svg { opacity: var(--wp-lm-o); animation: wp-lm-fade 2.4s ease-in-out infinite; }
215
+ @keyframes wp-lm-fade { 0%, 100% { --wp-lm-o: 1; } 50% { --wp-lm-o: 0.55; } }
216
+ }
217
+ </style>
package/src/Logo.vue CHANGED
@@ -1,60 +1,60 @@
1
- <script lang="ts">
2
- /** Gradient ids must be unique on the page, and two logos may wear different
3
- * brands, so count across every app on it. */
4
- let nextId = 0;
5
- </script>
6
-
7
- <script setup lang="ts">
8
- /**
9
- * The Wildcard Pipeline logo, drawn inline so it needs no file. It names
10
- * itself to screen readers; `decorative` hides it when the name is written
11
- * beside it (the top bar's brand). `brand="community"` draws it in the
12
- * community site's colours.
13
- *
14
- * <Logo :size="22" decorative />
15
- */
16
- import { computed } from "vue";
17
- import { BRAND_COLOURS, type LogoBrand } from "./logo-paths";
18
-
19
- const props = withDefaults(defineProps<{
20
- size?: number | undefined;
21
- decorative?: boolean | undefined;
22
- brand?: LogoBrand | undefined;
23
- }>(), { size: 28, brand: "extension" });
24
-
25
- const id = `wp-logo-${nextId++}`;
26
- const c = computed(() => BRAND_COLOURS[props.brand]);
27
- </script>
28
-
29
- <template>
30
- <svg
31
- xmlns="http://www.w3.org/2000/svg"
32
- viewBox="0 0 1024 1024"
33
- :width="size"
34
- :height="size"
35
- :role="decorative ? undefined : 'img'"
36
- :aria-label="decorative ? undefined : 'Wildcard Pipeline'"
37
- :aria-hidden="decorative || undefined"
38
- class="wp-logo"
39
- >
40
- <defs>
41
- <linearGradient :id="`${id}a`" x1="312.56" y1="897.19" x2="312.56" y2="2" gradientUnits="userSpaceOnUse">
42
- <stop offset="0" :stop-color="c[0]" />
43
- <stop offset="1" :stop-color="c[1]" />
44
- </linearGradient>
45
- <linearGradient :id="`${id}b`" x1="711.44" y1="126.81" x2="711.44" y2="1022" gradientUnits="userSpaceOnUse">
46
- <stop offset="0" :stop-color="c[1]" />
47
- <stop offset="1" :stop-color="c[2]" />
48
- </linearGradient>
49
- </defs>
50
- <path :fill="`url(#${id}a)`" d="m512,2c-53.6,0-98.47,38.16-108.84,88.73-75.07,19.29-143.32,58.26-198.74,113.68-55.42,55.42-94.39,123.68-113.68,198.74-50.58,10.38-88.73,55.24-88.73,108.84s38.16,98.47,88.73,108.84c13.62,53.14,37.03,102.64,69.77,147.47,37.14,50.86,85.83,93.86,140.81,124.33,5.54,3.08,11.56,4.54,17.48,4.54,12.7,0,25.02-6.72,31.61-18.61,9.68-17.44,3.38-39.42-14.07-49.09-85.32-47.32-147.22-125.71-173.38-218.21,36.3-18.29,61.27-55.92,61.27-99.28s-24.94-80.97-61.23-99.27c34.22-119.96,129.79-215.52,249.74-249.74,18.3,36.29,55.92,61.23,99.27,61.23,61.27,0,111.11-49.84,111.11-111.11S573.27,2,512,2ZM113.11,473.11c21.44,0,38.89,17.44,38.89,38.89,0,20.1-15.32,36.69-34.91,38.69h-.01c-.18.02-.36.03-.53.04-1.13.1-2.28.16-3.43.16-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89Zm398.89-321.11c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Z" />
51
- <path :fill="`url(#${id}b)`" d="m933.27,403.16c-13.62-53.14-37.03-102.64-69.77-147.47-37.14-50.86-85.83-93.86-140.81-124.33-17.44-9.68-39.42-3.38-49.09,14.07-9.68,17.44-3.38,39.42,14.07,49.09,85.32,47.32,147.22,125.71,173.38,218.21-36.3,18.29-61.27,55.92-61.27,99.28s24.94,80.97,61.23,99.27c-34.22,119.96-129.79,215.52-249.74,249.74-18.3-36.29-55.92-61.23-99.27-61.23-61.27,0-111.11,49.84-111.11,111.11s49.84,111.11,111.11,111.11c53.6,0,98.47-38.16,108.84-88.73,75.07-19.29,143.32-58.26,198.74-113.68,55.42-55.42,94.39-123.68,113.68-198.74,50.58-10.38,88.73-55.24,88.73-108.84s-38.16-98.47-88.73-108.84Zm-421.27,546.62c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Zm398.89-398.89c-21.44,0-38.89-17.44-38.89-38.89,0-20.03,15.22-36.58,34.72-38.67.38-.04.77-.08,1.16-.11h.02c.99-.08,1.99-.11,2.99-.11,21.44,0,38.89,17.44,38.89,38.89s-17.44,38.89-38.89,38.89Z" />
52
- <path :fill="c[0]" d="m735.02,517.56c-153.82,30.87-186.59,63.64-217.46,217.46-1.22,6.08-9.9,6.08-11.12,0-30.87-153.82-63.64-186.59-217.46-217.46-6.08-1.22-6.08-9.9,0-11.12,153.82-30.87,186.59-63.64,217.46-217.46,1.22-6.08,9.9-6.08,11.12,0,30.87,153.82,63.64,186.59,217.46,217.46,6.08,1.22,6.08,9.9,0,11.12Z" />
53
- <path :fill="c[1]" d="m434.06,665.85c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z" />
54
- <path :fill="c[2]" d="m738.07,361.84c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z" />
55
- </svg>
56
- </template>
57
-
58
- <style scoped>
59
- .wp-logo { display: block; flex-shrink: 0; }
60
- </style>
1
+ <script lang="ts">
2
+ /** Gradient ids must be unique on the page, and two logos may wear different
3
+ * brands, so count across every app on it. */
4
+ let nextId = 0;
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ /**
9
+ * The Wildcard Pipeline logo, drawn inline so it needs no file. It names
10
+ * itself to screen readers; `decorative` hides it when the name is written
11
+ * beside it (the top bar's brand). `brand="community"` draws it in the
12
+ * community site's colours.
13
+ *
14
+ * <Logo :size="22" decorative />
15
+ */
16
+ import { computed } from "vue";
17
+ import { BRAND_COLOURS, type LogoBrand } from "./logo-paths";
18
+
19
+ const props = withDefaults(defineProps<{
20
+ size?: number | undefined;
21
+ decorative?: boolean | undefined;
22
+ brand?: LogoBrand | undefined;
23
+ }>(), { size: 28, brand: "extension" });
24
+
25
+ const id = `wp-logo-${nextId++}`;
26
+ const c = computed(() => BRAND_COLOURS[props.brand]);
27
+ </script>
28
+
29
+ <template>
30
+ <svg
31
+ xmlns="http://www.w3.org/2000/svg"
32
+ viewBox="0 0 1024 1024"
33
+ :width="size"
34
+ :height="size"
35
+ :role="decorative ? undefined : 'img'"
36
+ :aria-label="decorative ? undefined : 'Wildcard Pipeline'"
37
+ :aria-hidden="decorative || undefined"
38
+ class="wp-logo"
39
+ >
40
+ <defs>
41
+ <linearGradient :id="`${id}a`" x1="312.56" y1="897.19" x2="312.56" y2="2" gradientUnits="userSpaceOnUse">
42
+ <stop offset="0" :stop-color="c[0]" />
43
+ <stop offset="1" :stop-color="c[1]" />
44
+ </linearGradient>
45
+ <linearGradient :id="`${id}b`" x1="711.44" y1="126.81" x2="711.44" y2="1022" gradientUnits="userSpaceOnUse">
46
+ <stop offset="0" :stop-color="c[1]" />
47
+ <stop offset="1" :stop-color="c[2]" />
48
+ </linearGradient>
49
+ </defs>
50
+ <path :fill="`url(#${id}a)`" d="m512,2c-53.6,0-98.47,38.16-108.84,88.73-75.07,19.29-143.32,58.26-198.74,113.68-55.42,55.42-94.39,123.68-113.68,198.74-50.58,10.38-88.73,55.24-88.73,108.84s38.16,98.47,88.73,108.84c13.62,53.14,37.03,102.64,69.77,147.47,37.14,50.86,85.83,93.86,140.81,124.33,5.54,3.08,11.56,4.54,17.48,4.54,12.7,0,25.02-6.72,31.61-18.61,9.68-17.44,3.38-39.42-14.07-49.09-85.32-47.32-147.22-125.71-173.38-218.21,36.3-18.29,61.27-55.92,61.27-99.28s-24.94-80.97-61.23-99.27c34.22-119.96,129.79-215.52,249.74-249.74,18.3,36.29,55.92,61.23,99.27,61.23,61.27,0,111.11-49.84,111.11-111.11S573.27,2,512,2ZM113.11,473.11c21.44,0,38.89,17.44,38.89,38.89,0,20.1-15.32,36.69-34.91,38.69h-.01c-.18.02-.36.03-.53.04-1.13.1-2.28.16-3.43.16-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89Zm398.89-321.11c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Z" />
51
+ <path :fill="`url(#${id}b)`" d="m933.27,403.16c-13.62-53.14-37.03-102.64-69.77-147.47-37.14-50.86-85.83-93.86-140.81-124.33-17.44-9.68-39.42-3.38-49.09,14.07-9.68,17.44-3.38,39.42,14.07,49.09,85.32,47.32,147.22,125.71,173.38,218.21-36.3,18.29-61.27,55.92-61.27,99.28s24.94,80.97,61.23,99.27c-34.22,119.96-129.79,215.52-249.74,249.74-18.3-36.29-55.92-61.23-99.27-61.23-61.27,0-111.11,49.84-111.11,111.11s49.84,111.11,111.11,111.11c53.6,0,98.47-38.16,108.84-88.73,75.07-19.29,143.32-58.26,198.74-113.68,55.42-55.42,94.39-123.68,113.68-198.74,50.58-10.38,88.73-55.24,88.73-108.84s-38.16-98.47-88.73-108.84Zm-421.27,546.62c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Zm398.89-398.89c-21.44,0-38.89-17.44-38.89-38.89,0-20.03,15.22-36.58,34.72-38.67.38-.04.77-.08,1.16-.11h.02c.99-.08,1.99-.11,2.99-.11,21.44,0,38.89,17.44,38.89,38.89s-17.44,38.89-38.89,38.89Z" />
52
+ <path :fill="c[0]" d="m735.02,517.56c-153.82,30.87-186.59,63.64-217.46,217.46-1.22,6.08-9.9,6.08-11.12,0-30.87-153.82-63.64-186.59-217.46-217.46-6.08-1.22-6.08-9.9,0-11.12,153.82-30.87,186.59-63.64,217.46-217.46,1.22-6.08,9.9-6.08,11.12,0,30.87,153.82,63.64,186.59,217.46,217.46,6.08,1.22,6.08,9.9,0,11.12Z" />
53
+ <path :fill="c[1]" d="m434.06,665.85c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z" />
54
+ <path :fill="c[2]" d="m738.07,361.84c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z" />
55
+ </svg>
56
+ </template>
57
+
58
+ <style scoped>
59
+ .wp-logo { display: block; flex-shrink: 0; }
60
+ </style>