@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
package/README.md CHANGED
@@ -1,76 +1,73 @@
1
- # wp-kit
2
-
3
- The Vue 3 component kit shared by the [Wildcard Pipeline](https://github.com/DumiFlex/ComfyUI-Wildcard-Pipeline) extension (its manager and canvas widgets) and the [Wildcard Pipeline Community](https://github.com/DumiFlex/Wildcard-Pipeline-Community) site.
4
-
5
- Buttons, inputs, selects, checkboxes and toggles, cards, chips, tables, modals, drawers and confirm dialogs, overlays (Popover, DropdownMenu, ContextMenu, tooltip), tabs, sliders, tag, date, colour and icon pickers, lists, image grid and lightbox, toasts, diff and code views, and the logo. And the theme they wear: the design tokens, six colour palettes and seven looks in `src/theme/`.
6
-
7
- Every piece uses CSS custom properties, so it takes on the colours, corners and type of whichever app it sits in.
8
-
9
- **See every piece live:** [dumiflex.github.io/wp-kit](https://dumiflex.github.io/wp-kit/), with a switcher for the palette, the look and light or dark.
10
-
11
- ## Install
12
-
13
- The kit is on npm as [`@dumiflex/wp-kit`](https://www.npmjs.com/package/@dumiflex/wp-kit). It ships its source (`.vue`, `.ts` and the theme CSS) with no build step, so your Vite app compiles the pieces it imports with its own Vue plugin:
14
-
15
- ```bash
16
- pnpm add @dumiflex/wp-kit vue primeicons
17
- ```
18
-
19
- ```ts
20
- import Button from "@dumiflex/wp-kit/Button.vue";
21
- import { SHARED_TOKENS } from "@dumiflex/wp-kit/tokens.ts";
22
- import "@dumiflex/wp-kit/theme/tokens.css";
23
- ```
24
-
25
- Import one file at a time (there is no barrel file, on purpose), so the bundler ships only what you use. In `vite.config`, set `resolve.dedupe: ["vue"]` (one Vue, not the app's and a second one) and `optimizeDeps.exclude: ["@dumiflex/wp-kit"]` (the `.vue` sources go through your Vue plugin, not the pre-bundler). An app that already imports the kit as `@/kit/...` can keep that by aliasing `@/kit` to `node_modules/@dumiflex/wp-kit/src`.
26
-
27
- Each release is staged on npm by the release workflow (trusted publishing, with provenance) and goes live once a maintainer approves it.
28
-
29
- ### Apps that copy the kit
30
-
31
- The Wildcard Pipeline apps still copy `src/` in at a release tag until they move to the package:
32
-
33
- ```bash
34
- node scripts/kit-pull.mjs v0.2.1 # in the app repo
35
- ```
36
-
37
- The app's script downloads that tag, copies `src/` into the app's kit folder, and writes the tag and commit next to it. A CI check (`kit-pull.mjs --check`) fails when the copy no longer matches what was pulled, so a fix to a kit piece is made here, released, and pulled into the apps in an ordinary PR.
38
-
39
- ## What a host must provide
40
-
41
- - **Vue 3.5** and **PrimeIcons 7** (peer dependencies; icons are `pi pi-*` classes). The kit imports nothing else at runtime.
42
- - **A theme.** A page app loads the kit's own: `theme/tokens.css`, then `theme/palettes.css` and `theme/looks.css`, and `button.css`. It sets `data-wp-palette`, `data-wp-look` and `wp-theme-light` on `<html>` to switch them (see [docs/tokens.md](docs/tokens.md)). A host with its own colour sheet (the ComfyUI canvas) defines the names in `SHARED_TOKENS` ([`src/tokens.ts`](src/tokens.ts)) instead, and uses only the pieces that read nothing else.
43
- - **`button.css`** loaded once globally if the app uses the `.wp-btn` classes on plain elements. `WpButton` pulls it in on its own.
44
-
45
- ## Rules for a piece
46
-
47
- - Reads only the shared token names, or, for a piece listed in `PAGE_PIECES`, names `src/theme/` defines (`src/tokens.test.ts` fails otherwise).
48
- - Every class starts with `wp-`.
49
- - Fixed corners are `min(Npx, var(--wp-radius-cap, 999px))`, pills `var(--wp-radius-cap, 999px)`, so a host's look can square them off.
50
- - Props use the shared names `size` / `variant` / `tone` / `v-model:open`; heights come from `--wp-control-h-sm|md|lg` (24 / 32 / 40 px).
51
- - Overlays place themselves through `floating.ts` and handle Escape, Tab and focus return through `useDialogFocus`.
52
- - No app code: no stores, routers, ComfyUI globals or `@/` imports. A piece only imports `vue` and other kit files.
53
-
54
- Every piece has an entry and a demo on the site (`site/src/registry.ts` + `site/src/demos/<id>.vue`); `site/src/coverage.test.ts` fails when one is missing. The longer component standard lives in the extension repo: `docs/component-standard.md`.
55
-
56
- ## Develop
57
-
58
- ```bash
59
- pnpm install
60
- pnpm lint
61
- pnpm typecheck
62
- pnpm test
63
- pnpm site:dev # the showcase site on localhost
64
- pnpm site:build # site/dist, what Pages serves
65
- pnpm check-package # what npm would publish: the files apps import, nothing repo-only
66
- ```
67
-
68
- Node 22+ and pnpm 9. The site lives in `site/` (outside `src/`, so the apps never copy it). It is deployed to GitHub Pages by `.github/workflows/site.yml` on every release and on "Run workflow"; CI builds it on every PR.
69
-
70
- ## Versions and releases
71
-
72
- Commits and PR titles follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `refactor:` …); the `pr-title` check enforces it. Work goes into `dev` by squash-merged PRs. Merging `dev` into `main` ships: release-please opens a release PR with the next version and `CHANGELOG.md`, it merges itself once CI passes, and the `vX.Y.Z` tag it makes is what the apps pull. While the kit is below 1.0, a `feat` bumps the minor version and a `fix` the patch.
73
-
74
- ## License
75
-
76
- [GPL-3.0-or-later](LICENSE). Copyright (C) 2026 DumiFlex.
1
+ # @dumiflex/wp-kit
2
+
3
+ [![npm](https://img.shields.io/npm/v/@dumiflex/wp-kit?logo=npm&label=npm)](https://www.npmjs.com/package/@dumiflex/wp-kit)
4
+ [![Vue 3](https://img.shields.io/badge/Vue-3-42b883?logo=vuedotjs&logoColor=white)](https://vuejs.org/)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
+ [![License: GPL-3.0-or-later](https://img.shields.io/npm/l/@dumiflex/wp-kit)](https://github.com/DumiFlex/wp-kit/blob/main/LICENSE)
7
+
8
+ A Vue 3 component kit with its own theme: buttons, fields, selects, checkboxes, switches, chips, cards, tables, dialogs, drawers, popovers, menus, tooltips, tabs, sliders, colour, date and icon pickers, lists, toasts, code and diff views. The theme is design tokens, six colour palettes and seven looks (corners, type, borders). It was built for [Wildcard Pipeline](https://github.com/DumiFlex/ComfyUI-Wildcard-Pipeline), a ComfyUI extension, and works in any Vue 3 + Vite app.
9
+
10
+ Every piece reads CSS custom properties, so it takes on the colours, corners and type of the app it sits in.
11
+
12
+ **See every piece live, with when to use it:** [dumiflex.github.io/wp-kit](https://dumiflex.github.io/wp-kit/). The site has a switcher for the palette, the look and light or dark.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @dumiflex/wp-kit vue primeicons
18
+ # or: pnpm add @dumiflex/wp-kit vue primeicons
19
+ ```
20
+
21
+ The package ships source (`.vue`, `.ts` and CSS) with no build step, and your Vite app compiles the pieces it imports. In `vite.config`:
22
+
23
+ ```ts
24
+ export default defineConfig({
25
+ plugins: [vue()],
26
+ resolve: { dedupe: ["vue"] }, // one Vue, yours
27
+ optimizeDeps: { exclude: ["@dumiflex/wp-kit"] }, // .vue files go through your Vue plugin
28
+ });
29
+ ```
30
+
31
+ ## Use
32
+
33
+ Import one file at a time. There is no barrel file, so the bundler only ships what you use:
34
+
35
+ ```ts
36
+ import "primeicons/primeicons.css";
37
+ import "@dumiflex/wp-kit/theme/tokens.css";
38
+ import "@dumiflex/wp-kit/theme/palettes.css";
39
+ import "@dumiflex/wp-kit/theme/looks.css";
40
+ import "@dumiflex/wp-kit/button.css";
41
+
42
+ import Button from "@dumiflex/wp-kit/Button.vue";
43
+ import Chip from "@dumiflex/wp-kit/Chip.vue";
44
+ ```
45
+
46
+ ```vue
47
+ <Button variant="primary" icon="pi-plus" @click="add">Add</Button>
48
+ <Chip tone="warn" icon="pi-exclamation-triangle">2 conflicts</Chip>
49
+ <Chip :pressed="tag === 'portrait'" :count="12" size="sm" @click="tag = 'portrait'">portrait</Chip>
50
+ ```
51
+
52
+ Pick a palette and a look on `<html>`: `data-wp-palette="…"`, `data-wp-look="…"`, and the class `wp-theme-light` for light mode. The site lists them all and shows each piece's props and an example.
53
+
54
+ If you already import the kit as `@/kit/...`, alias `@/kit` to `node_modules/@dumiflex/wp-kit/src`.
55
+
56
+ ## What your app provides
57
+
58
+ - **Vue 3.5** and **PrimeIcons 7** (peer dependencies). Icons are `pi pi-*` classes. The kit imports nothing else at runtime.
59
+ - **A theme:** the kit's own sheets above. A host with its own colour sheet can define the names in [`SHARED_TOKENS`](https://github.com/DumiFlex/wp-kit/blob/main/src/tokens.ts) instead and use the pieces that read only those.
60
+
61
+ ## Versions
62
+
63
+ [Semantic versioning](https://semver.org/). Below 1.0, a minor version can break: read the [changelog](https://github.com/DumiFlex/wp-kit/blob/main/CHANGELOG.md) before you bump. Each release is published from CI with provenance.
64
+
65
+ ## Links
66
+
67
+ - [Live components](https://dumiflex.github.io/wp-kit/)
68
+ - [Source and issues](https://github.com/DumiFlex/wp-kit)
69
+ - [Changelog](https://github.com/DumiFlex/wp-kit/blob/main/CHANGELOG.md)
70
+
71
+ ## License
72
+
73
+ [GPL-3.0-or-later](https://github.com/DumiFlex/wp-kit/blob/main/LICENSE). Copyright (C) 2026 DumiFlex.
package/package.json CHANGED
@@ -1,77 +1,78 @@
1
- {
2
- "name": "@dumiflex/wp-kit",
3
- "version": "0.3.0",
4
- "description": "Vue 3 component kit shared by the Wildcard Pipeline manager, canvas and community site.",
5
- "keywords": [
6
- "vue",
7
- "vue3",
8
- "components",
9
- "design-tokens",
10
- "comfyui",
11
- "wildcard-pipeline"
12
- ],
13
- "homepage": "https://dumiflex.github.io/wp-kit/",
14
- "bugs": {
15
- "url": "https://github.com/DumiFlex/wp-kit/issues"
16
- },
17
- "license": "GPL-3.0-or-later",
18
- "type": "module",
19
- "files": [
20
- "src",
21
- "!src/**/__tests__",
22
- "!src/**/*.test.ts",
23
- "CHANGELOG.md"
24
- ],
25
- "exports": {
26
- "./package.json": "./package.json",
27
- "./*": "./src/*"
28
- },
29
- "sideEffects": [
30
- "**/*.css",
31
- "**/*.vue"
32
- ],
33
- "publishConfig": {
34
- "access": "public"
35
- },
36
- "repository": {
37
- "type": "git",
38
- "url": "git+https://github.com/DumiFlex/wp-kit.git"
39
- },
40
- "packageManager": "pnpm@9.12.0",
41
- "engines": {
42
- "node": ">=22"
43
- },
44
- "scripts": {
45
- "lint": "eslint .",
46
- "typecheck": "vue-tsc --noEmit",
47
- "test": "vitest run",
48
- "test:watch": "vitest",
49
- "site:dev": "vite --config site/vite.config.ts",
50
- "site:build": "vite build --config site/vite.config.ts",
51
- "site:preview": "vite preview --config site/vite.config.ts",
52
- "check-package": "node scripts/check-package.mjs"
53
- },
54
- "peerDependencies": {
55
- "vue": "^3.5.0",
56
- "primeicons": "^7.0.0 || ^8.0.0"
57
- },
58
- "devDependencies": {
59
- "@types/node": "^22.10.0",
60
- "@typescript-eslint/eslint-plugin": "8.59.0",
61
- "@typescript-eslint/parser": "8.59.0",
62
- "@vitejs/plugin-vue": "5.2.4",
63
- "@vue/test-utils": "2.4.8",
64
- "@vue/tsconfig": "0.5.1",
65
- "eslint": "9.39.4",
66
- "eslint-plugin-vue": "9.33.0",
67
- "eslint-plugin-vuejs-accessibility": "^2.5.0",
68
- "jsdom": "29.0.2",
69
- "primeicons": "7.0.0",
70
- "typescript": "5.9.3",
71
- "vite": "5.4.21",
72
- "vitest": "2.1.9",
73
- "vue": "3.5.44",
74
- "vue-eslint-parser": "10.4.0",
75
- "vue-tsc": "2.2.12"
76
- }
77
- }
1
+ {
2
+ "name": "@dumiflex/wp-kit",
3
+ "version": "0.4.0",
4
+ "description": "Vue 3 component kit shared by the Wildcard Pipeline manager, canvas and community site.",
5
+ "keywords": [
6
+ "vue",
7
+ "vue3",
8
+ "components",
9
+ "design-tokens",
10
+ "comfyui",
11
+ "wildcard-pipeline"
12
+ ],
13
+ "homepage": "https://dumiflex.github.io/wp-kit/",
14
+ "bugs": {
15
+ "url": "https://github.com/DumiFlex/wp-kit/issues"
16
+ },
17
+ "license": "GPL-3.0-or-later",
18
+ "type": "module",
19
+ "files": [
20
+ "src",
21
+ "!src/**/__tests__",
22
+ "!src/**/*.test.ts",
23
+ "CHANGELOG.md"
24
+ ],
25
+ "exports": {
26
+ "./package.json": "./package.json",
27
+ "./*": "./src/*"
28
+ },
29
+ "sideEffects": [
30
+ "**/*.css",
31
+ "**/*.vue"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/DumiFlex/wp-kit.git"
39
+ },
40
+ "packageManager": "pnpm@9.12.0",
41
+ "engines": {
42
+ "node": ">=22"
43
+ },
44
+ "scripts": {
45
+ "lint": "eslint .",
46
+ "typecheck": "vue-tsc --noEmit",
47
+ "test": "vitest run",
48
+ "test:watch": "vitest",
49
+ "site:dev": "vite --config site/vite.config.ts",
50
+ "site:build": "vite build --config site/vite.config.ts",
51
+ "site:preview": "vite preview --config site/vite.config.ts",
52
+ "check-package": "node scripts/npm-readme.mjs --check && node scripts/check-package.mjs",
53
+ "npm-readme": "node scripts/npm-readme.mjs"
54
+ },
55
+ "peerDependencies": {
56
+ "vue": "^3.5.0",
57
+ "primeicons": "^7.0.0 || ^8.0.0"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^22.10.0",
61
+ "@typescript-eslint/eslint-plugin": "8.59.0",
62
+ "@typescript-eslint/parser": "8.59.0",
63
+ "@vitejs/plugin-vue": "5.2.4",
64
+ "@vue/test-utils": "2.4.8",
65
+ "@vue/tsconfig": "0.5.1",
66
+ "eslint": "9.39.4",
67
+ "eslint-plugin-vue": "9.33.0",
68
+ "eslint-plugin-vuejs-accessibility": "^2.5.0",
69
+ "jsdom": "29.0.2",
70
+ "primeicons": "7.0.0",
71
+ "typescript": "5.9.3",
72
+ "vite": "5.4.21",
73
+ "vitest": "2.1.9",
74
+ "vue": "3.5.44",
75
+ "vue-eslint-parser": "10.4.0",
76
+ "vue-tsc": "2.2.12"
77
+ }
78
+ }
package/src/Avatar.vue CHANGED
@@ -1,83 +1,83 @@
1
- <script setup lang="ts">
2
- /**
3
- * Avatar: a person's picture, or their initials on a colour picked from
4
- * their name when there is no picture (or it fails to load). For community
5
- * authors, chat participants and history entries. Works on the canvas and
6
- * in the manager.
7
- *
8
- * <Avatar name="Dumi Flex" :src="author.avatar_url" />
9
- * <Avatar name="Pack bot" icon="pi-box" size="sm" />
10
- *
11
- * The name is the image's alt text; with `decorative` (the name is already
12
- * written beside it) screen readers skip it.
13
- */
14
- import { computed, ref, watch } from "vue";
15
-
16
- const props = withDefaults(defineProps<{
17
- name: string;
18
- src?: string | null | undefined;
19
- /** A PrimeIcons class to draw instead of initials. */
20
- icon?: string | undefined;
21
- size?: "sm" | "md" | "lg" | undefined;
22
- shape?: "circle" | "square" | undefined;
23
- /** The name is written next to it already. */
24
- decorative?: boolean | undefined;
25
- }>(), { src: null, icon: undefined, size: "md", shape: "circle" });
26
-
27
- const failed = ref(false);
28
- watch(() => props.src, () => { failed.value = false; });
29
-
30
- const initials = computed(() => {
31
- const words = props.name.trim().split(/\s+/).filter(Boolean);
32
- if (!words.length) return "?";
33
- const first = [...words[0]][0] ?? "";
34
- const last = words.length > 1 ? [...words[words.length - 1]][0] ?? "" : "";
35
- return (first + last).toUpperCase();
36
- });
37
-
38
- const HUES = [
39
- "var(--wp-var-1)", "var(--wp-var-2)", "var(--wp-var-3)", "var(--wp-var-4)",
40
- "var(--wp-var-5)", "var(--wp-var-6)", "var(--wp-var-7)", "var(--wp-var-8)",
41
- ];
42
-
43
- /** One of the eight variable colours, the same for the same name every time. */
44
- const hue = computed(() => {
45
- let h = 0;
46
- for (const ch of props.name) h = (h * 31 + (ch.codePointAt(0) ?? 0)) >>> 0;
47
- return HUES[h % 8];
48
- });
49
- </script>
50
-
51
- <template>
52
- <span
53
- class="wp-avatar"
54
- :data-size="size"
55
- :data-shape="shape"
56
- :style="{ '--_hue': hue }"
57
- :role="decorative ? undefined : 'img'"
58
- :aria-label="decorative ? undefined : name"
59
- :aria-hidden="decorative ? 'true' : undefined"
60
- >
61
- <img v-if="src && !failed" class="wp-avatar__img" :src="src" alt="" @error="failed = true">
62
- <i v-else-if="icon" class="pi wp-avatar__icon" :class="icon" aria-hidden="true" />
63
- <span v-else class="wp-avatar__initials" aria-hidden="true">{{ initials }}</span>
64
- </span>
65
- </template>
66
-
67
- <style scoped>
68
- .wp-avatar {
69
- --_s: var(--wp-control-h-md);
70
- position: relative; display: inline-grid; place-items: center; flex: none; overflow: hidden;
71
- width: var(--_s); height: var(--_s); border-radius: 50%; /* round: avatar */
72
- background: color-mix(in srgb, var(--_hue) 22%, var(--wp-bg-2));
73
- color: color-mix(in srgb, var(--_hue) 70%, var(--wp-text));
74
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--_hue) 30%, transparent);
75
- font: 600 calc(var(--_s) * 0.4)/1 var(--wp-font-sans); letter-spacing: 0.02em; user-select: none;
76
- }
77
- .wp-avatar[data-size="sm"] { --_s: var(--wp-control-h-sm); }
78
- .wp-avatar[data-size="lg"] { --_s: var(--wp-control-h-lg); }
79
- .wp-avatar[data-shape="square"] { border-radius: var(--wp-radius); }
80
- .wp-avatar[data-size="sm"][data-shape="square"] { border-radius: var(--wp-radius-sm); }
81
- .wp-avatar__img { width: 100%; height: 100%; object-fit: cover; }
82
- .wp-avatar__icon { font-size: calc(var(--_s) * 0.45); }
83
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Avatar: a person's picture, or their initials on a colour picked from
4
+ * their name when there is no picture (or it fails to load). For community
5
+ * authors, chat participants and history entries. Works on the canvas and
6
+ * in the manager.
7
+ *
8
+ * <Avatar name="Dumi Flex" :src="author.avatar_url" />
9
+ * <Avatar name="Pack bot" icon="pi-box" size="sm" />
10
+ *
11
+ * The name is the image's alt text; with `decorative` (the name is already
12
+ * written beside it) screen readers skip it.
13
+ */
14
+ import { computed, ref, watch } from "vue";
15
+
16
+ const props = withDefaults(defineProps<{
17
+ name: string;
18
+ src?: string | null | undefined;
19
+ /** A PrimeIcons class to draw instead of initials. */
20
+ icon?: string | undefined;
21
+ size?: "sm" | "md" | "lg" | undefined;
22
+ shape?: "circle" | "square" | undefined;
23
+ /** The name is written next to it already. */
24
+ decorative?: boolean | undefined;
25
+ }>(), { src: null, icon: undefined, size: "md", shape: "circle" });
26
+
27
+ const failed = ref(false);
28
+ watch(() => props.src, () => { failed.value = false; });
29
+
30
+ const initials = computed(() => {
31
+ const words = props.name.trim().split(/\s+/).filter(Boolean);
32
+ if (!words.length) return "?";
33
+ const first = [...words[0]][0] ?? "";
34
+ const last = words.length > 1 ? [...words[words.length - 1]][0] ?? "" : "";
35
+ return (first + last).toUpperCase();
36
+ });
37
+
38
+ const HUES = [
39
+ "var(--wp-var-1)", "var(--wp-var-2)", "var(--wp-var-3)", "var(--wp-var-4)",
40
+ "var(--wp-var-5)", "var(--wp-var-6)", "var(--wp-var-7)", "var(--wp-var-8)",
41
+ ];
42
+
43
+ /** One of the eight variable colours, the same for the same name every time. */
44
+ const hue = computed(() => {
45
+ let h = 0;
46
+ for (const ch of props.name) h = (h * 31 + (ch.codePointAt(0) ?? 0)) >>> 0;
47
+ return HUES[h % 8];
48
+ });
49
+ </script>
50
+
51
+ <template>
52
+ <span
53
+ class="wp-avatar"
54
+ :data-size="size"
55
+ :data-shape="shape"
56
+ :style="{ '--_hue': hue }"
57
+ :role="decorative ? undefined : 'img'"
58
+ :aria-label="decorative ? undefined : name"
59
+ :aria-hidden="decorative ? 'true' : undefined"
60
+ >
61
+ <img v-if="src && !failed" class="wp-avatar__img" :src="src" alt="" @error="failed = true">
62
+ <i v-else-if="icon" class="pi wp-avatar__icon" :class="icon" aria-hidden="true" />
63
+ <span v-else class="wp-avatar__initials" aria-hidden="true">{{ initials }}</span>
64
+ </span>
65
+ </template>
66
+
67
+ <style scoped>
68
+ .wp-avatar {
69
+ --_s: var(--wp-control-h-md);
70
+ position: relative; display: inline-grid; place-items: center; flex: none; overflow: hidden;
71
+ width: var(--_s); height: var(--_s); border-radius: 50%; /* round: avatar */
72
+ background: color-mix(in srgb, var(--_hue) 22%, var(--wp-bg-2));
73
+ color: color-mix(in srgb, var(--_hue) 70%, var(--wp-text));
74
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--_hue) 30%, transparent);
75
+ font: 600 calc(var(--_s) * 0.4)/1 var(--wp-font-sans); letter-spacing: 0.02em; user-select: none;
76
+ }
77
+ .wp-avatar[data-size="sm"] { --_s: var(--wp-control-h-sm); }
78
+ .wp-avatar[data-size="lg"] { --_s: var(--wp-control-h-lg); }
79
+ .wp-avatar[data-shape="square"] { border-radius: var(--wp-radius); }
80
+ .wp-avatar[data-size="sm"][data-shape="square"] { border-radius: var(--wp-radius-sm); }
81
+ .wp-avatar__img { width: 100%; height: 100%; object-fit: cover; }
82
+ .wp-avatar__icon { font-size: calc(var(--_s) * 0.45); }
83
+ </style>