@dumiflex/wp-kit 0.3.0 → 0.4.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 (128) hide show
  1. package/CHANGELOG.md +77 -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 +119 -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 +292 -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 +125 -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 +173 -173
  44. package/src/ListFilter.vue +85 -85
  45. package/src/ListRow.vue +204 -204
  46. package/src/LoadingMark.vue +175 -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 +82 -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/hsv.ts +100 -100
  101. package/src/icon-catalog.ts +52 -52
  102. package/src/image-zoom.ts +93 -93
  103. package/src/kind-colour.ts +28 -0
  104. package/src/logo-paths.ts +21 -21
  105. package/src/matrix-axis.ts +91 -91
  106. package/src/pagination.ts +27 -27
  107. package/src/platform-keys.ts +29 -29
  108. package/src/rich-tokenize.ts +357 -357
  109. package/src/select-types.ts +32 -32
  110. package/src/slider-math.ts +45 -45
  111. package/src/sortable.ts +22 -22
  112. package/src/theme/fonts/OFL-Fraunces.txt +93 -93
  113. package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -93
  114. package/src/theme/fonts/OFL-Manrope.txt +93 -93
  115. package/src/theme/looks.css +428 -428
  116. package/src/theme/palettes.css +254 -254
  117. package/src/theme/tokens.css +2896 -2894
  118. package/src/tokens.ts +56 -56
  119. package/src/tooltip.css +43 -43
  120. package/src/tooltip.ts +146 -146
  121. package/src/useDialogFocus.ts +73 -73
  122. package/src/useGrowableField.ts +357 -357
  123. package/src/useModalKeyShield.ts +116 -116
  124. package/src/useToast.ts +48 -48
  125. package/src/wp-logo.svg +14 -14
  126. package/src/ChipToggle.vue +0 -92
  127. package/src/KindChip.vue +0 -53
  128. package/src/RatingBadge.vue +0 -42
@@ -1,161 +1,175 @@
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, 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
+ /* Every move animates a registered custom property, never `transform` or
123
+ * `opacity` itself. A browser hands a `transform` / `opacity` animation to
124
+ * the compositor, which lifts the path onto its own layer the moment the
125
+ * animation starts: the layer is rasterised once, at a fixed scale, and
126
+ * snapped to whole pixels, so the crisp mark turned soft and jumped by up to
127
+ * half a pixel as it began to move. A custom property animates on the main
128
+ * thread, so each frame repaints the vector paths in place, as sharp as the
129
+ * mark at rest. The paths are tiny; the repaint costs nothing. */
130
+ .wp-lm__ring { transform-box: view-box; transform-origin: 512px 512px; }
131
+ .wp-lm__star { transform-box: fill-box; transform-origin: center; }
132
+ .wp-lm__ring--a { transform: translate(var(--wp-lm-x), var(--wp-lm-x)); animation: wp-lm-split-a var(--wp-lm-t) cubic-bezier(0.65, 0, 0.35, 1) infinite; }
133
+ .wp-lm__ring--b { transform: translate(var(--wp-lm-x), var(--wp-lm-x)); animation: wp-lm-split-b var(--wp-lm-t) cubic-bezier(0.65, 0, 0.35, 1) infinite; }
134
+ .wp-lm__star--big { transform: rotate(var(--wp-lm-r)) scale(var(--wp-lm-s)); animation: wp-lm-dip var(--wp-lm-t) cubic-bezier(0.65, 0, 0.35, 1) infinite; }
135
+ .wp-lm__star--tw { opacity: var(--wp-lm-o); transform: scale(var(--wp-lm-s)); animation: wp-lm-twinkle var(--wp-lm-t) ease-in-out infinite; }
136
+ .wp-lm__swap {
137
+ transform-box: view-box; transform-origin: 512px 512px;
138
+ transform: rotate(var(--wp-lm-r));
139
+ animation: wp-lm-swap calc(var(--wp-lm-t) * 2) cubic-bezier(0.65, 0, 0.35, 1) infinite;
140
+ }
141
+
142
+ @property --wp-lm-x { syntax: "<length>"; inherits: false; initial-value: 0px; }
143
+ @property --wp-lm-r { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
144
+ @property --wp-lm-s { syntax: "<number>"; inherits: false; initial-value: 1; }
145
+ @property --wp-lm-o { syntax: "<number>"; inherits: false; initial-value: 1; }
146
+
147
+ @keyframes wp-lm-split-a {
148
+ 0%, 15%, 85%, 100% { --wp-lm-x: 0px; }
149
+ 40%, 60% { --wp-lm-x: -70px; }
150
+ }
151
+ @keyframes wp-lm-split-b {
152
+ 0%, 15%, 85%, 100% { --wp-lm-x: 0px; }
153
+ 40%, 60% { --wp-lm-x: 70px; }
154
+ }
155
+ @keyframes wp-lm-dip {
156
+ 0%, 15% { --wp-lm-s: 1; --wp-lm-r: 0deg; }
157
+ 32% { --wp-lm-s: 0.7; --wp-lm-r: 45deg; }
158
+ 50%, 100% { --wp-lm-s: 1; --wp-lm-r: 90deg; }
159
+ }
160
+ @keyframes wp-lm-swap {
161
+ 0%, 15% { --wp-lm-r: 0deg; }
162
+ 50%, 65% { --wp-lm-r: 180deg; }
163
+ 100% { --wp-lm-r: 360deg; }
164
+ }
165
+ @keyframes wp-lm-twinkle {
166
+ 0%, 100% { --wp-lm-o: 1; --wp-lm-s: 1; }
167
+ 50% { --wp-lm-o: 0.35; --wp-lm-s: 0.6; }
168
+ }
169
+
170
+ @media (prefers-reduced-motion: reduce) {
171
+ .wp-lm__svg * { animation: none !important; }
172
+ .wp-lm__svg { opacity: var(--wp-lm-o); animation: wp-lm-fade 2.4s ease-in-out infinite; }
173
+ @keyframes wp-lm-fade { 0%, 100% { --wp-lm-o: 1; } 50% { --wp-lm-o: 0.55; } }
174
+ }
175
+ </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>
package/src/Modal.vue CHANGED
@@ -1,76 +1,78 @@
1
- <script setup lang="ts">
2
- import CloseButton from "./CloseButton.vue";
3
- import { computed, ref, useId, useSlots } from "vue";
4
- import { useDialogFocus } from "./useDialogFocus";
5
-
6
- interface Props {
7
- open: boolean;
8
- title?: string | undefined;
9
- size?: "sm" | "md" | "lg" | undefined;
10
- closeOnBackdrop?: boolean | undefined;
11
- }
12
- const props = withDefaults(defineProps<Props>(), { size: "md", closeOnBackdrop: true });
13
-
14
- const emit = defineEmits<{
15
- (e: "update:open", v: boolean): void;
16
- }>();
17
-
18
- const slots = useSlots();
19
-
20
- const modalClasses = computed(() => ["wp-modal", `wp-modal--${props.size}`]);
21
- const dialog = ref<HTMLElement | null>(null);
22
- const titleId = `wp-modal-title-${useId()}`;
23
-
24
- function close() {
25
- emit("update:open", false);
26
- }
27
-
28
- function onBackdrop() {
29
- if (props.closeOnBackdrop) close();
30
- }
31
-
32
- useDialogFocus(dialog, () => props.open, {
33
- onEscape: close,
34
- // The first control in the body, else the dialog itself (the close button
35
- // would make Enter close it).
36
- initial: (root, els) => {
37
- const body = root.querySelector(".wp-modal__body");
38
- return els.find((el) => body?.contains(el)) ?? root;
39
- },
40
- });
41
- </script>
42
-
43
- <template>
44
- <Teleport to="body">
45
- <div
46
- v-if="open"
47
- class="wp-modal__backdrop"
48
- data-test="modal-backdrop"
49
- @mousedown.self="onBackdrop"
50
- >
51
- <div
52
- ref="dialog"
53
- :class="modalClasses"
54
- role="dialog"
55
- aria-modal="true"
56
- :aria-labelledby="title || slots.header ? titleId : undefined"
57
- tabindex="-1"
58
- @mousedown.stop
59
- >
60
- <header v-if="title || slots.header" class="wp-modal__head">
61
- <h3 v-if="title" :id="titleId" class="wp-modal__title">{{ title }}</h3>
62
- <!-- A custom header puts :id="titleId" on its own heading. -->
63
- <slot name="header" :title-id="titleId" />
64
- <span class="wp-spacer" />
65
- <CloseButton kbd="Esc" @click="close" />
66
- </header>
67
- <div class="wp-modal__body">
68
- <slot />
69
- </div>
70
- <footer v-if="slots.footer" class="wp-modal__foot">
71
- <slot name="footer" />
72
- </footer>
73
- </div>
74
- </div>
75
- </Teleport>
76
- </template>
1
+ <script setup lang="ts">
2
+ import CloseButton from "./CloseButton.vue";
3
+ import { computed, ref, useId, useSlots } from "vue";
4
+ import { useDialogFocus } from "./useDialogFocus";
5
+
6
+ interface Props {
7
+ open: boolean;
8
+ title?: string | undefined;
9
+ size?: "sm" | "md" | "lg" | undefined;
10
+ closeOnBackdrop?: boolean | undefined;
11
+ }
12
+ const props = withDefaults(defineProps<Props>(), { size: "md", closeOnBackdrop: true });
13
+
14
+ const emit = defineEmits<{
15
+ (e: "update:open", v: boolean): void;
16
+ }>();
17
+
18
+ const slots = useSlots();
19
+
20
+ const modalClasses = computed(() => ["wp-modal", `wp-modal--${props.size}`]);
21
+ const dialog = ref<HTMLElement | null>(null);
22
+ const titleId = `wp-modal-title-${useId()}`;
23
+
24
+ function close() {
25
+ emit("update:open", false);
26
+ }
27
+
28
+ function onBackdrop() {
29
+ if (props.closeOnBackdrop) close();
30
+ }
31
+
32
+ useDialogFocus(dialog, () => props.open, {
33
+ onEscape: close,
34
+ // The first control in the body, else the dialog itself (the close button
35
+ // would make Enter close it).
36
+ initial: (root, els) => {
37
+ const body = root.querySelector(".wp-modal__body");
38
+ return els.find((el) => body?.contains(el)) ?? root;
39
+ },
40
+ });
41
+ </script>
42
+
43
+ <template>
44
+ <Teleport to="body">
45
+ <!-- The backdrop is a click target only; keyboard users close with Escape. -->
46
+ <!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -->
47
+ <div
48
+ v-if="open"
49
+ class="wp-modal__backdrop"
50
+ data-test="modal-backdrop"
51
+ @mousedown.self="onBackdrop"
52
+ >
53
+ <div
54
+ ref="dialog"
55
+ :class="modalClasses"
56
+ role="dialog"
57
+ aria-modal="true"
58
+ :aria-labelledby="title || slots.header ? titleId : undefined"
59
+ tabindex="-1"
60
+ @mousedown.stop
61
+ >
62
+ <header v-if="title || slots.header" class="wp-modal__head">
63
+ <h3 v-if="title" :id="titleId" class="wp-modal__title">{{ title }}</h3>
64
+ <!-- A custom header puts :id="titleId" on its own heading. -->
65
+ <slot name="header" :title-id="titleId" />
66
+ <span class="wp-spacer" />
67
+ <CloseButton kbd="Esc" @click="close" />
68
+ </header>
69
+ <div class="wp-modal__body">
70
+ <slot />
71
+ </div>
72
+ <footer v-if="slots.footer" class="wp-modal__foot">
73
+ <slot name="footer" />
74
+ </footer>
75
+ </div>
76
+ </div>
77
+ </Teleport>
78
+ </template>