@dumiflex/wp-kit 0.0.0-stage → 0.3.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 +51 -0
  2. package/LICENSE +674 -0
  3. package/README.md +76 -3
  4. package/package.json +77 -6
  5. package/src/Avatar.vue +83 -0
  6. package/src/Badge.vue +97 -0
  7. package/src/Banner.vue +118 -0
  8. package/src/BookmarkButton.vue +27 -0
  9. package/src/BundleMeta.vue +51 -0
  10. package/src/Button.vue +103 -0
  11. package/src/Card.vue +68 -0
  12. package/src/Checkbox.vue +107 -0
  13. package/src/Chip.vue +97 -0
  14. package/src/ChipToggle.vue +92 -0
  15. package/src/ChoiceCards.vue +133 -0
  16. package/src/CloseButton.vue +69 -0
  17. package/src/CodeBlock.vue +119 -0
  18. package/src/ColorField.vue +206 -0
  19. package/src/ColorPicker.vue +201 -0
  20. package/src/Combobox.vue +334 -0
  21. package/src/ConfirmDialog.vue +163 -0
  22. package/src/ConfirmModal.vue +147 -0
  23. package/src/ConstraintMatrixPreview.vue +708 -0
  24. package/src/ContextMenu.vue +379 -0
  25. package/src/CopyButton.vue +156 -0
  26. package/src/DataTable.vue +292 -0
  27. package/src/DatePicker.vue +234 -0
  28. package/src/DescriptionList.vue +73 -0
  29. package/src/DiffView.vue +68 -0
  30. package/src/Disclosure.vue +166 -0
  31. package/src/DragHandle.vue +49 -0
  32. package/src/Drawer.vue +131 -0
  33. package/src/DropZone.vue +196 -0
  34. package/src/DropdownMenu.vue +72 -0
  35. package/src/EmptyState.vue +111 -0
  36. package/src/Field.vue +28 -0
  37. package/src/HsvPicker.vue +208 -0
  38. package/src/Icon.vue +39 -0
  39. package/src/IconPicker.vue +209 -0
  40. package/src/ImageGrid.vue +125 -0
  41. package/src/InlineEdit.vue +183 -0
  42. package/src/Input.vue +99 -0
  43. package/src/Kbd.vue +56 -0
  44. package/src/KindChip.vue +53 -0
  45. package/src/Lightbox.vue +173 -0
  46. package/src/ListFilter.vue +85 -0
  47. package/src/ListRow.vue +204 -0
  48. package/src/LoadingMark.vue +161 -0
  49. package/src/Logo.vue +60 -0
  50. package/src/Modal.vue +76 -0
  51. package/src/ModalShell.vue +107 -0
  52. package/src/Pagination.vue +106 -0
  53. package/src/Popover.vue +171 -0
  54. package/src/PostCard.vue +211 -0
  55. package/src/ProgressBar.vue +90 -0
  56. package/src/RadioGroup.vue +177 -0
  57. package/src/RangeSlider.vue +183 -0
  58. package/src/RatingBadge.vue +42 -0
  59. package/src/RelativeDate.vue +35 -0
  60. package/src/ResizeHandle.vue +143 -0
  61. package/src/SearchInput.vue +199 -0
  62. package/src/Select.vue +641 -0
  63. package/src/Separator.vue +54 -0
  64. package/src/Skeleton.vue +82 -0
  65. package/src/Slider.vue +198 -0
  66. package/src/SortableList.vue +260 -0
  67. package/src/SplitPane.vue +68 -0
  68. package/src/StarButton.vue +40 -0
  69. package/src/Stepper.vue +141 -0
  70. package/src/SupportLinks.vue +93 -0
  71. package/src/TagInput.vue +300 -0
  72. package/src/Textarea.vue +57 -0
  73. package/src/Timeline.vue +92 -0
  74. package/src/ToastCard.vue +119 -0
  75. package/src/ToastHost.vue +40 -0
  76. package/src/Toggle.vue +36 -0
  77. package/src/TokenizedValue.vue +310 -0
  78. package/src/Toolbar.vue +110 -0
  79. package/src/VirtualList.vue +87 -0
  80. package/src/VisuallyHidden.vue +29 -0
  81. package/src/WpButton.vue +75 -0
  82. package/src/WpCheck.vue +102 -0
  83. package/src/WpIconButton.vue +107 -0
  84. package/src/WpImageZoom.vue +544 -0
  85. package/src/WpNumber.vue +183 -0
  86. package/src/WpSegmented.vue +175 -0
  87. package/src/WpSelect.vue +89 -0
  88. package/src/WpSwitch.vue +163 -0
  89. package/src/WpTabs.vue +188 -0
  90. package/src/_modal-motion.css +33 -0
  91. package/src/_skeleton.css +50 -0
  92. package/src/axis-color.ts +42 -0
  93. package/src/button.css +169 -0
  94. package/src/calendar.ts +59 -0
  95. package/src/clipboard.ts +50 -0
  96. package/src/colour-presets.ts +12 -0
  97. package/src/diff.ts +51 -0
  98. package/src/drop-zone.ts +41 -0
  99. package/src/flip.ts +237 -0
  100. package/src/floating.ts +99 -0
  101. package/src/hsv.ts +100 -0
  102. package/src/icon-catalog.ts +52 -0
  103. package/src/image-zoom.ts +93 -0
  104. package/src/logo-paths.ts +21 -0
  105. package/src/matrix-axis.ts +91 -0
  106. package/src/pagination.ts +27 -0
  107. package/src/platform-keys.ts +29 -0
  108. package/src/rich-tokenize.ts +357 -0
  109. package/src/select-types.ts +32 -0
  110. package/src/slider-math.ts +45 -0
  111. package/src/sortable.ts +22 -0
  112. package/src/theme/fonts/OFL-Fraunces.txt +93 -0
  113. package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -0
  114. package/src/theme/fonts/OFL-Manrope.txt +93 -0
  115. package/src/theme/fonts/fraunces-latin-wght-normal.woff2 +0 -0
  116. package/src/theme/fonts/jetbrains-mono-latin.woff2 +0 -0
  117. package/src/theme/fonts/manrope-latin-wght-normal.woff2 +0 -0
  118. package/src/theme/looks.css +428 -0
  119. package/src/theme/palettes.css +254 -0
  120. package/src/theme/tokens.css +2894 -0
  121. package/src/tokens.ts +56 -0
  122. package/src/tooltip.css +43 -0
  123. package/src/tooltip.ts +146 -0
  124. package/src/useDialogFocus.ts +73 -0
  125. package/src/useGrowableField.ts +357 -0
  126. package/src/useModalKeyShield.ts +116 -0
  127. package/src/useToast.ts +48 -0
  128. package/src/wp-logo.svg +14 -0
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts" generic="T">
2
+ /**
3
+ * Virtual list: a long list (thousands of options, every library item)
4
+ * where only the rows in view are built, so it scrolls smoothly. Rows have
5
+ * one fixed height. Works on the canvas and in the manager.
6
+ *
7
+ * <VirtualList :items="options" :item-height="32" height="320px" :item-key="(o) => o.id" label="Options">
8
+ * <template #item="{ item, index }">…</template>
9
+ * </VirtualList>
10
+ *
11
+ * The rows keep their place in the list for screen readers
12
+ * (`aria-setsize` / `aria-posinset`). `scrollToIndex(i)` brings a row into
13
+ * view, e.g. the one a keyboard user moved to.
14
+ */
15
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
16
+
17
+ const props = withDefaults(defineProps<{
18
+ items: T[];
19
+ itemKey: (item: T, index: number) => string | number;
20
+ /** Every row's height in px. */
21
+ itemHeight: number;
22
+ /** CSS height of the scrolling box. */
23
+ height?: string | undefined;
24
+ /** Rows built above and below the view. */
25
+ overscan?: number | undefined;
26
+ label?: string | undefined;
27
+ }>(), { height: "320px", overscan: 6, label: undefined });
28
+
29
+ const boxEl = ref<HTMLElement | null>(null);
30
+ const top = ref(0);
31
+ const viewH = ref(320);
32
+
33
+ const first = computed(() => Math.max(0, Math.floor(top.value / props.itemHeight) - props.overscan));
34
+ const last = computed(() => Math.min(props.items.length, Math.ceil((top.value + viewH.value) / props.itemHeight) + props.overscan));
35
+ const shown = computed(() => props.items.slice(first.value, last.value).map((item, k) => ({ item, index: first.value + k })));
36
+
37
+ function onScroll(): void {
38
+ top.value = boxEl.value?.scrollTop ?? 0;
39
+ }
40
+
41
+ let ro: ResizeObserver | null = null;
42
+ onMounted(() => {
43
+ viewH.value = boxEl.value?.clientHeight || viewH.value;
44
+ if (typeof ResizeObserver !== "undefined" && boxEl.value) {
45
+ ro = new ResizeObserver(() => { viewH.value = boxEl.value?.clientHeight || viewH.value; });
46
+ ro.observe(boxEl.value);
47
+ }
48
+ });
49
+ onBeforeUnmount(() => ro?.disconnect());
50
+
51
+ /** Scroll so row `i` is in view (nearest edge). */
52
+ function scrollToIndex(i: number): void {
53
+ const el = boxEl.value;
54
+ if (!el) return;
55
+ const y = i * props.itemHeight;
56
+ if (y < el.scrollTop) el.scrollTop = y;
57
+ else if (y + props.itemHeight > el.scrollTop + el.clientHeight) el.scrollTop = y + props.itemHeight - el.clientHeight;
58
+ onScroll();
59
+ }
60
+
61
+ defineExpose({ scrollToIndex });
62
+ </script>
63
+
64
+ <template>
65
+ <div ref="boxEl" class="wp-vlist" :style="{ height }" tabindex="0" role="list" :aria-label="label" @scroll.passive="onScroll">
66
+ <div class="wp-vlist__space" :style="{ height: `${items.length * itemHeight}px` }">
67
+ <div
68
+ v-for="row in shown"
69
+ :key="itemKey(row.item, row.index)"
70
+ class="wp-vlist__row"
71
+ role="listitem"
72
+ :aria-setsize="items.length"
73
+ :aria-posinset="row.index + 1"
74
+ :style="{ top: `${row.index * itemHeight}px`, height: `${itemHeight}px` }"
75
+ >
76
+ <slot name="item" :item="row.item" :index="row.index" />
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </template>
81
+
82
+ <style scoped>
83
+ .wp-vlist { position: relative; overflow-y: auto; overscroll-behavior: contain; min-width: 0; outline: none; border-radius: var(--wp-radius); }
84
+ .wp-vlist:focus-visible { box-shadow: inset 0 0 0 2px var(--wp-border-focus); }
85
+ .wp-vlist__space { position: relative; width: 100%; }
86
+ .wp-vlist__row { position: absolute; left: 0; right: 0; display: flex; align-items: center; min-width: 0; box-sizing: border-box; }
87
+ </style>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Visually hidden: text for screen readers only (a table header with no
4
+ * visible name, "(opens in a new tab)", a live status line). With
5
+ * `focusable` it shows itself while it has focus, for a "Skip to the list"
6
+ * link. Works on the canvas and in the manager.
7
+ *
8
+ * <VisuallyHidden>Select</VisuallyHidden>
9
+ * <VisuallyHidden as="div" aria-live="polite">{{ status }}</VisuallyHidden>
10
+ */
11
+ withDefaults(defineProps<{
12
+ /** The element to draw. */
13
+ as?: string | undefined;
14
+ /** Show while focused (skip links). */
15
+ focusable?: boolean | undefined;
16
+ }>(), { as: "span" });
17
+ </script>
18
+
19
+ <template>
20
+ <component :is="as" class="wp-visually-hidden" :data-focusable="focusable || undefined"><slot /></component>
21
+ </template>
22
+
23
+ <style scoped>
24
+ .wp-visually-hidden:not([data-focusable]),
25
+ .wp-visually-hidden[data-focusable]:not(:focus):not(:focus-within) {
26
+ position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
27
+ clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
28
+ }
29
+ </style>
@@ -0,0 +1,75 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The canvas twin of the manager Button (manager/components/ui/Button.vue).
4
+ * Same variants, sizes and states, same stylesheet (`button.css`), but the
5
+ * icon is a PrimeIcons class, as everywhere on the canvas, and the default
6
+ * size is the canvas `sm`.
7
+ *
8
+ * <WpButton variant="primary" icon="pi-plus">Add module</WpButton>
9
+ * <WpButton variant="ghost" icon="pi-download" aria-label="Download snapshot" />
10
+ * <WpButton variant="add" icon="pi-plus" block>Add rule</WpButton>
11
+ *
12
+ * With no slot content it is an icon button (`.wp-btn--icon`); its
13
+ * `aria-label` is also its tooltip (`hint` sets another, `kbd` adds a key
14
+ * chip). `pressed` turns it into a toggle (a "favourites only" star, a
15
+ * filter panel trigger). Loading swaps the icon for the sparkle, sets
16
+ * aria-busy and ignores clicks.
17
+ *
18
+ * The stylesheet is pulled in scoped, so the canvas never carries the
19
+ * `.wp-btn` classes globally. Row icon buttons are WpIconButton.
20
+ */
21
+ import { computed, useSlots } from "vue";
22
+ import LoadingMark from "./LoadingMark.vue";
23
+ import { vTooltip } from "./tooltip";
24
+
25
+ const props = withDefaults(defineProps<{
26
+ variant?: "primary" | "secondary" | "ghost" | "danger" | "link" | "outline" | "add" | undefined;
27
+ size?: "sm" | "md" | "lg" | undefined;
28
+ /** PrimeIcons class (e.g. `pi-plus`), drawn before the label. */
29
+ icon?: string | undefined;
30
+ loading?: boolean | undefined;
31
+ disabled?: boolean | undefined;
32
+ /** Stretch to the container's full width. */
33
+ block?: boolean | undefined;
34
+ /** Makes it a toggle (`aria-pressed`): on, it wears the accent tint. */
35
+ pressed?: boolean | undefined;
36
+ type?: "button" | "submit" | "reset" | undefined;
37
+ ariaLabel?: string | undefined;
38
+ /** Tooltip text; an icon button shows its aria-label when this is unset.
39
+ * Use this, not `v-tooltip` on the component, which would fight the
40
+ * button's own. */
41
+ hint?: string | undefined;
42
+ /** A shortcut shown in the tooltip's key chip, e.g. "P". */
43
+ kbd?: string | undefined;
44
+ }>(), { variant: "secondary", size: "sm", icon: undefined, type: "button", ariaLabel: undefined, hint: undefined, kbd: undefined, pressed: undefined });
45
+
46
+ const emit = defineEmits<{ click: [evt: MouseEvent] }>();
47
+
48
+ const slots = useSlots();
49
+ const iconOnly = computed(() => !slots.default);
50
+
51
+ function onClick(e: MouseEvent): void {
52
+ if (props.disabled || props.loading) return;
53
+ emit("click", e);
54
+ }
55
+ </script>
56
+
57
+ <template>
58
+ <button
59
+ v-tooltip="{ text: hint ?? (iconOnly ? ariaLabel : undefined), kbd }"
60
+ :type="type"
61
+ class="wp-btn"
62
+ :class="[`wp-btn--${variant}`, `wp-btn--${size}`, { 'wp-btn--icon': iconOnly, 'wp-btn--block': block }]"
63
+ :disabled="disabled || loading"
64
+ :aria-label="ariaLabel"
65
+ :aria-busy="loading || undefined"
66
+ :aria-pressed="pressed === undefined ? undefined : pressed"
67
+ @click="onClick"
68
+ >
69
+ <LoadingMark v-if="loading" variant="sparkle" mono :size="size === 'lg' ? 16 : size === 'md' ? 14 : 12" decorative />
70
+ <i v-else-if="icon" class="pi" :class="icon" aria-hidden="true" />
71
+ <slot />
72
+ </button>
73
+ </template>
74
+
75
+ <style scoped src="./button.css"></style>
@@ -0,0 +1,102 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Shared checkbox box — the wildcard pool's per-option look (`.opt__check`
4
+ * in OptionRow.vue), extracted so every checkmark box in the *embed/canvas*
5
+ * tree reads the same.
6
+ *
7
+ * Why a component (not the global `.wp-check` class): the canvas bundle only
8
+ * loads a hand-picked CSS set via `src/extension/settings.ts` — it does NOT
9
+ * load the SPA's `manager/styles/tokens.css`, where global `.wp-check` lives.
10
+ * A component's `<style scoped>` is compiled into the component's own chunk,
11
+ * so it ships wherever the component renders — SPA *and* canvas. That's the
12
+ * same reason `.opt__check` / `.row__check` / `.ex__check` are each scoped.
13
+ *
14
+ * Custom toggle BUTTONS (internal flag, seed-lock) stay as their own styled
15
+ * buttons — this is only for true checkmark boxes.
16
+ *
17
+ * `aria-label` / `data-test` fall through to the single root span. Click /
18
+ * Space / Enter toggle. `size="sm"` is the 12px box of the canvas rows' on/off
19
+ * mark (modules, bundles, injector rows). A parent can tint the checked fill
20
+ * with `--check-on` (a bundle header uses its bundle colour).
21
+ */
22
+ const props = withDefaults(
23
+ defineProps<{
24
+ /** Checked state — use with `v-model` or `:model-value` + `@update:model-value`. */
25
+ modelValue?: boolean | undefined;
26
+ disabled?: boolean | undefined;
27
+ size?: "sm" | "md" | undefined;
28
+ }>(),
29
+ { modelValue: false, disabled: false, size: "md" },
30
+ );
31
+
32
+ const emit = defineEmits<{ "update:modelValue": [next: boolean] }>();
33
+
34
+ function toggle(): void {
35
+ if (props.disabled) return;
36
+ emit("update:modelValue", !props.modelValue);
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <span
42
+ class="wp-check"
43
+ :class="{ 'wp-check--sm': size === 'sm' }"
44
+ role="checkbox"
45
+ :aria-checked="modelValue === true"
46
+ :aria-disabled="disabled || undefined"
47
+ :tabindex="disabled ? -1 : 0"
48
+ @click="toggle"
49
+ @keydown.space.prevent="toggle"
50
+ @keydown.enter.prevent="toggle"
51
+ >
52
+ <!-- 8×8 SVG tick inside the 14px box — identical to OptionRow's
53
+ `.opt__check` so the canvas boxes match the wildcard options. -->
54
+ <svg
55
+ v-if="modelValue"
56
+ width="8"
57
+ height="8"
58
+ viewBox="0 0 12 12"
59
+ aria-hidden="true"
60
+ >
61
+ <path
62
+ d="M2.5 6.5 L5 9 L9.5 3.5"
63
+ fill="none"
64
+ stroke="currentColor"
65
+ stroke-width="2"
66
+ stroke-linecap="round"
67
+ stroke-linejoin="round"
68
+ />
69
+ </svg>
70
+ </span>
71
+ </template>
72
+
73
+ <style scoped>
74
+ /* Verbatim from OptionRow.vue `.opt__check`. Scoped so it ships into the
75
+ * canvas bundle (no global tokens.css there). Vars resolve from the SPA's
76
+ * tokens.css or the canvas theme.css — both define these. */
77
+ .wp-check {
78
+ width: 14px;
79
+ height: 14px;
80
+ border: 1.5px solid var(--wp-border-soft, var(--wp-border));
81
+ border-radius: min(3px, var(--wp-radius-cap, 999px));
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ color: white;
86
+ background: var(--wp-bg);
87
+ flex-shrink: 0;
88
+ cursor: pointer;
89
+ }
90
+ .wp-check svg { display: block; }
91
+ .wp-check--sm { width: 12px; height: 12px; border-width: 1px; border-radius: min(2px, var(--wp-radius-cap, 999px)); }
92
+ .wp-check--sm svg { width: 7px; height: 7px; }
93
+ .wp-check:focus-visible { outline: none; box-shadow: var(--wp-focus-ring); }
94
+ .wp-check[aria-checked="true"] {
95
+ background: var(--check-on, var(--wp-accent));
96
+ border-color: var(--check-on, var(--wp-accent));
97
+ }
98
+ .wp-check[aria-disabled="true"] {
99
+ cursor: not-allowed;
100
+ opacity: 0.45;
101
+ }
102
+ </style>
@@ -0,0 +1,107 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * WpIconButton: the 20 px square icon button inside a canvas row (module,
4
+ * injector, bundle and model rows, the Context toolbar). Rows are too tight
5
+ * for the 24 px `WpButton size="sm"`, so this is the one smaller control on
6
+ * the canvas, still at the 20 px hit-target floor.
7
+ *
8
+ * <WpIconButton icon="pi-trash" label="Remove module" tone="danger" @click="remove" />
9
+ * <WpIconButton icon="pi-lock" label="Lock seed" tone="warn" :pressed="locked" />
10
+ * <WpIconButton variant="bare" :icon="open ? 'pi-caret-down' : 'pi-caret-right'" label="Collapse" />
11
+ *
12
+ * - `tone` tints the icon on hover, and the whole button while `pressed`.
13
+ * - `pressed` makes it a toggle (`aria-pressed`); `"mixed"` is the bundle
14
+ * master toggle's "some children" state (dashed, half tint).
15
+ * - `variant="bare"` is the row's fold caret: no box, drawn 14 px wide but
16
+ * still 20 px to click.
17
+ * - `label` names it for screen readers and is its tooltip, unless `hint`
18
+ * gives a longer one.
19
+ */
20
+ import { vTooltip } from "./tooltip";
21
+
22
+ withDefaults(defineProps<{
23
+ /** PrimeIcons class, e.g. `pi-trash`. Leave it out to draw a short
24
+ * glyph from the slot instead (the loop's Σ toggle). */
25
+ icon?: string | undefined;
26
+ label: string;
27
+ /** A longer tooltip than the label. */
28
+ hint?: string | undefined;
29
+ tone?: "neutral" | "accent" | "warn" | "danger" | undefined;
30
+ pressed?: boolean | "mixed" | undefined;
31
+ variant?: "quiet" | "bare" | undefined;
32
+ disabled?: boolean | undefined;
33
+ }>(), { icon: undefined, hint: undefined, tone: "neutral", pressed: undefined, variant: "quiet" });
34
+
35
+ const emit = defineEmits<{ click: [evt: MouseEvent] }>();
36
+ </script>
37
+
38
+ <template>
39
+ <button
40
+ v-tooltip="hint ?? label"
41
+ type="button"
42
+ class="wp-icon-btn"
43
+ :data-tone="tone"
44
+ :data-variant="variant"
45
+ :aria-label="label"
46
+ :aria-pressed="pressed"
47
+ :disabled="disabled"
48
+ @click="emit('click', $event)"
49
+ >
50
+ <i v-if="icon" class="pi" :class="icon" aria-hidden="true" />
51
+ <span v-else class="wp-icon-btn__glyph" aria-hidden="true"><slot /></span>
52
+ </button>
53
+ </template>
54
+
55
+ <style scoped>
56
+ .wp-icon-btn {
57
+ --tone: var(--wp-text);
58
+ display: inline-flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ flex: none;
62
+ box-sizing: border-box;
63
+ width: 20px;
64
+ height: 20px;
65
+ margin: 0;
66
+ padding: 0;
67
+ border: 1px solid var(--wp-border-strong);
68
+ border-radius: var(--wp-radius);
69
+ background: var(--wp-bg-2);
70
+ color: var(--wp-text);
71
+ font: 500 11px/1 var(--wp-font-sans);
72
+ cursor: pointer;
73
+ transition: background var(--wp-motion-quick), border-color var(--wp-motion-quick), color var(--wp-motion-quick);
74
+ }
75
+ .wp-icon-btn[data-tone="accent"] { --tone: var(--wp-accent); }
76
+ .wp-icon-btn[data-tone="warn"] { --tone: var(--wp-warn); }
77
+ .wp-icon-btn[data-tone="danger"] { --tone: var(--wp-danger); }
78
+ .wp-icon-btn .pi { font-size: 11px; }
79
+ .wp-icon-btn__glyph { font: 600 11px/1 var(--wp-font-mono); }
80
+ .wp-icon-btn:hover:not(:disabled) { background: var(--wp-bg-4); color: var(--tone); }
81
+ .wp-icon-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp-border-focus); }
82
+ .wp-icon-btn:disabled { opacity: 0.45; cursor: not-allowed; }
83
+ /* On: the tone tints the icon and washes the box, the same tint as hover. */
84
+ .wp-icon-btn[aria-pressed="true"] {
85
+ color: var(--tone);
86
+ background: color-mix(in srgb, var(--tone) 14%, transparent);
87
+ }
88
+ .wp-icon-btn[aria-pressed="mixed"] {
89
+ color: var(--tone);
90
+ background: color-mix(in srgb, var(--tone) 7%, transparent);
91
+ border-style: dashed;
92
+ opacity: 0.75;
93
+ }
94
+ /* The fold caret: no box, 14 px of layout, 20 px of hit area. */
95
+ .wp-icon-btn[data-variant="bare"] {
96
+ width: 20px;
97
+ margin-inline: -3px;
98
+ border-color: transparent;
99
+ background: none;
100
+ color: var(--wp-text-dim);
101
+ }
102
+ .wp-icon-btn[data-variant="bare"] .pi { font-size: 10px; }
103
+ .wp-icon-btn[data-variant="bare"]:hover:not(:disabled) { background: none; color: var(--wp-text); }
104
+ @media (prefers-reduced-motion: reduce) {
105
+ .wp-icon-btn { transition: none; }
106
+ }
107
+ </style>