@ptbdynamic/plinth 0.0.0-stage → 0.1.1

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/LICENSE +21 -0
  2. package/README.md +258 -2
  3. package/package.json +99 -4
  4. package/packages/css/dist/avatar.css +122 -0
  5. package/packages/css/dist/badge.css +76 -0
  6. package/packages/css/dist/button.css +273 -0
  7. package/packages/css/dist/callout.css +143 -0
  8. package/packages/css/dist/code-block.css +283 -0
  9. package/packages/css/dist/confirm-dialog.css +183 -0
  10. package/packages/css/dist/data-table.css +470 -0
  11. package/packages/css/dist/dialog.css +113 -0
  12. package/packages/css/dist/drawer.css +156 -0
  13. package/packages/css/dist/fonts/geist/LICENSE +93 -0
  14. package/packages/css/dist/fonts/geist/geist-cyrillic-ext-wght-normal.woff2 +0 -0
  15. package/packages/css/dist/fonts/geist/geist-cyrillic-wght-normal.woff2 +0 -0
  16. package/packages/css/dist/fonts/geist/geist-latin-ext-wght-normal.woff2 +0 -0
  17. package/packages/css/dist/fonts/geist/geist-latin-wght-normal.woff2 +0 -0
  18. package/packages/css/dist/fonts/geist/geist-vietnamese-wght-normal.woff2 +0 -0
  19. package/packages/css/dist/fonts/geist-mono/LICENSE +93 -0
  20. package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-ext-wght-normal.woff2 +0 -0
  21. package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-wght-normal.woff2 +0 -0
  22. package/packages/css/dist/fonts/geist-mono/geist-mono-latin-ext-wght-normal.woff2 +0 -0
  23. package/packages/css/dist/fonts/geist-mono/geist-mono-latin-wght-normal.woff2 +0 -0
  24. package/packages/css/dist/fonts/geist-mono/geist-mono-symbols2-wght-normal.woff2 +0 -0
  25. package/packages/css/dist/fonts/geist-mono/geist-mono-vietnamese-wght-normal.woff2 +0 -0
  26. package/packages/css/dist/fonts/index.css +109 -0
  27. package/packages/css/dist/header.css +110 -0
  28. package/packages/css/dist/icon.css +51 -0
  29. package/packages/css/dist/list-card.css +76 -0
  30. package/packages/css/dist/list-row.css +164 -0
  31. package/packages/css/dist/menu-sheet.css +84 -0
  32. package/packages/css/dist/modal.css +367 -0
  33. package/packages/css/dist/pill-row.css +197 -0
  34. package/packages/css/dist/plinth.css +3773 -0
  35. package/packages/css/dist/section-menu.css +225 -0
  36. package/packages/css/dist/sheet.css +239 -0
  37. package/packages/css/dist/tokens.css +405 -0
  38. package/packages/react/dist/PlinthAvatar.d.ts +17 -0
  39. package/packages/react/dist/PlinthBadge.d.ts +7 -0
  40. package/packages/react/dist/PlinthButton.d.ts +29 -0
  41. package/packages/react/dist/PlinthCallout.d.ts +16 -0
  42. package/packages/react/dist/PlinthCodeBlock.d.ts +24 -0
  43. package/packages/react/dist/PlinthConfirmDialog.d.ts +38 -0
  44. package/packages/react/dist/PlinthDataTable.d.ts +11 -0
  45. package/packages/react/dist/PlinthDataTableCell.d.ts +15 -0
  46. package/packages/react/dist/PlinthDataTableColumn.d.ts +13 -0
  47. package/packages/react/dist/PlinthDataTableEmpty.d.ts +9 -0
  48. package/packages/react/dist/PlinthDataTableRow.d.ts +9 -0
  49. package/packages/react/dist/PlinthDataTableSort.d.ts +4 -0
  50. package/packages/react/dist/PlinthDialog.d.ts +26 -0
  51. package/packages/react/dist/PlinthDrawer.d.ts +29 -0
  52. package/packages/react/dist/PlinthHeader.d.ts +12 -0
  53. package/packages/react/dist/PlinthIcon.d.ts +10 -0
  54. package/packages/react/dist/PlinthIconButton.d.ts +26 -0
  55. package/packages/react/dist/PlinthListCard.d.ts +14 -0
  56. package/packages/react/dist/PlinthListRow.d.ts +29 -0
  57. package/packages/react/dist/PlinthMenuSheet.d.ts +24 -0
  58. package/packages/react/dist/PlinthPill.d.ts +17 -0
  59. package/packages/react/dist/PlinthPillRow.d.ts +13 -0
  60. package/packages/react/dist/PlinthSectionMenu.d.ts +11 -0
  61. package/packages/react/dist/PlinthSectionMenuItem.d.ts +20 -0
  62. package/packages/react/dist/PlinthSheet.d.ts +26 -0
  63. package/packages/react/dist/avatar.d.ts +12 -0
  64. package/packages/react/dist/button.d.ts +21 -0
  65. package/packages/react/dist/callout.d.ts +19 -0
  66. package/packages/react/dist/code-block.d.ts +16 -0
  67. package/packages/react/dist/confirm-dialog.d.ts +23 -0
  68. package/packages/react/dist/data-table.d.ts +15 -0
  69. package/packages/react/dist/dialog.d.ts +10 -0
  70. package/packages/react/dist/drawer.d.ts +20 -0
  71. package/packages/react/dist/heading.d.ts +5 -0
  72. package/packages/react/dist/icon.d.ts +5 -0
  73. package/packages/react/dist/index.d.ts +40 -0
  74. package/packages/react/dist/index.js +1127 -0
  75. package/packages/react/dist/list-row.d.ts +5 -0
  76. package/packages/react/dist/markup.d.ts +9 -0
  77. package/packages/react/dist/sheet.d.ts +12 -0
  78. package/packages/react/dist/size.d.ts +13 -0
  79. package/packages/react/dist/swipe.d.ts +15 -0
  80. package/packages/react/dist/tones.d.ts +5 -0
  81. package/packages/tokens/dist/index.d.ts +218 -0
  82. package/packages/tokens/dist/index.js +3966 -0
  83. package/packages/tokens/dist/tailwind-theme.css +80 -0
  84. package/packages/tokens/dist/tokens.css +405 -0
  85. package/packages/tokens/dist/tokens.json +3762 -0
  86. package/packages/vue/dist/PlinthAvatar.vue.d.ts +24 -0
  87. package/packages/vue/dist/PlinthBadge.vue.d.ts +21 -0
  88. package/packages/vue/dist/PlinthButton.vue.d.ts +42 -0
  89. package/packages/vue/dist/PlinthCallout.vue.d.ts +31 -0
  90. package/packages/vue/dist/PlinthCodeBlock.vue.d.ts +49 -0
  91. package/packages/vue/dist/PlinthConfirmDialog.vue.d.ts +51 -0
  92. package/packages/vue/dist/PlinthDataTable.vue.d.ts +21 -0
  93. package/packages/vue/dist/PlinthDataTableCell.vue.d.ts +29 -0
  94. package/packages/vue/dist/PlinthDataTableColumn.vue.d.ts +26 -0
  95. package/packages/vue/dist/PlinthDataTableEmpty.vue.d.ts +19 -0
  96. package/packages/vue/dist/PlinthDataTableRow.vue.d.ts +19 -0
  97. package/packages/vue/dist/PlinthDataTableSort.vue.d.ts +13 -0
  98. package/packages/vue/dist/PlinthDialog.vue.d.ts +45 -0
  99. package/packages/vue/dist/PlinthDrawer.vue.d.ts +48 -0
  100. package/packages/vue/dist/PlinthHeader.vue.d.ts +19 -0
  101. package/packages/vue/dist/PlinthIcon.vue.d.ts +23 -0
  102. package/packages/vue/dist/PlinthIconButton.vue.d.ts +37 -0
  103. package/packages/vue/dist/PlinthListCard.vue.d.ts +26 -0
  104. package/packages/vue/dist/PlinthListRow.vue.d.ts +38 -0
  105. package/packages/vue/dist/PlinthMenuSheet.vue.d.ts +48 -0
  106. package/packages/vue/dist/PlinthPill.vue.d.ts +28 -0
  107. package/packages/vue/dist/PlinthPillRow.vue.d.ts +22 -0
  108. package/packages/vue/dist/PlinthSectionMenu.vue.d.ts +22 -0
  109. package/packages/vue/dist/PlinthSectionMenuItem.vue.d.ts +28 -0
  110. package/packages/vue/dist/PlinthSheet.vue.d.ts +45 -0
  111. package/packages/vue/dist/avatar.d.ts +12 -0
  112. package/packages/vue/dist/button.d.ts +5 -0
  113. package/packages/vue/dist/callout.d.ts +19 -0
  114. package/packages/vue/dist/code-block.d.ts +16 -0
  115. package/packages/vue/dist/confirm-dialog.d.ts +23 -0
  116. package/packages/vue/dist/data-table.d.ts +15 -0
  117. package/packages/vue/dist/dialog.d.ts +10 -0
  118. package/packages/vue/dist/drawer.d.ts +20 -0
  119. package/packages/vue/dist/heading.d.ts +5 -0
  120. package/packages/vue/dist/icon.d.ts +5 -0
  121. package/packages/vue/dist/index.d.ts +44 -0
  122. package/packages/vue/dist/index.js +1439 -0
  123. package/packages/vue/dist/list-row.d.ts +5 -0
  124. package/packages/vue/dist/sheet.d.ts +12 -0
  125. package/packages/vue/dist/size.d.ts +13 -0
  126. package/packages/vue/dist/slots.d.ts +3 -0
  127. package/packages/vue/dist/swipe.d.ts +12 -0
  128. package/packages/vue/dist/tones.d.ts +5 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BDynamic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,259 @@
1
- # Temporary Holding Version
1
+ # Plinth
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The design system of BDynamic's projects: design tokens, components as plain HTML and CSS, and
4
+ thin Blade, Vue and React layers that print the same markup, documented and tested in Storybook.
5
+
6
+ - **Tokens** are the source of truth: colour (light and dark), type, spacing, radii, layers,
7
+ breakpoints and motion. They are written once and built to CSS custom properties, a JS/TS
8
+ export and a Tailwind v4 theme file.
9
+ - **Components are plain HTML and CSS.** Each one has a markup contract (its elements, classes,
10
+ states and ARIA) that every framework layer prints.
11
+ - **Blade, Vue and React layers** print that markup and hold no styles of their own.
12
+ - **Storybook** documents the plain HTML and tests it: accessibility and parity
13
+ between the story, Blade, Vue and React.
14
+ - **Each project brings its brand through the tokens.** Plinth ships neutral defaults; a project
15
+ overrides the custom properties and never forks a component.
16
+
17
+ ## Layout
18
+
19
+ ```
20
+ packages/
21
+ tokens/ the tokens in DTCG JSON (tokens/*.json), built with Style Dictionary to CSS custom
22
+ properties, a JS/TS export and a Tailwind theme
23
+ css/ one stylesheet per component, tokens only, mobile first; builds dist/plinth.css
24
+ vue/ Vue 3 components that print the markup contract
25
+ react/ React 19 components that print the same markup contract
26
+ blade/ Composer package: Blade components, a Filament plugin, and the built CSS
27
+ storybook/ Storybook (HTML renderer): component stories, their docs pages, parity tests
28
+ site/ Plinth's website, a Laravel app built with Plinth's components, and the guides in
29
+ site/docs (see site/README.md)
30
+ ```
31
+
32
+ The npm workspaces are private. They are published together as one npm package and one Composer
33
+ package (nothing is published yet):
34
+
35
+ | Package | Registry | Holds |
36
+ | -------------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
37
+ | `@ptbdynamic/plinth` | npm | `./tokens`, `./tokens.css`, `./css`, `./vue`, `./react` and `./tailwind.css` |
38
+ | `ptbdynamic/plinth` | Packagist | `<x-plinth::…>` Blade components, `PlinthPlugin` for Filament, the built CSS and fonts in `resources/dist` |
39
+
40
+ Packagist reads the Composer package from the `composer.json` at the root of the repository, which
41
+ points its autoload and Laravel provider at `packages/blade/`. `.gitattributes` marks everything
42
+ else `export-ignore`, so a `composer require` downloads only `packages/blade/src`,
43
+ `packages/blade/resources`, `composer.json`, `README.md` and `LICENSE` (and `CHANGELOG.md` once
44
+ there is one). `packages/blade/composer.json` names the same package for the tests, the dev tools
45
+ and the site's path repository.
46
+
47
+ ## Working on Plinth
48
+
49
+ You need Node 22 (see `.nvmrc`), PHP 8.4 and Composer. `npm ci` also runs `composer install` in
50
+ `packages/blade`.
51
+
52
+ ```bash
53
+ npm ci
54
+ npx playwright install chromium # once, for the Storybook test runner
55
+
56
+ npm run storybook # http://127.0.0.1:6006 (builds the tokens and CSS first)
57
+ npm run build-storybook # static build in storybook/storybook-static
58
+ npm run check # everything CI runs (turbo run lint test build build-storybook)
59
+ ```
60
+
61
+ [Turborepo](https://turborepo.com) runs each task in the order the packages depend on each other
62
+ (`tokens` → `css` → `vue`, `react` and `blade` → `storybook` and `site`) and caches the results in `.turbo/`, so a
63
+ second run with nothing changed is served from the cache. On a small machine, add
64
+ `--concurrency=2`.
65
+
66
+ `turbo.json` passes the `MISE_*`, `__MISE_*` and `LERD_*` variables through to tasks. Turborepo
67
+ strips the environment by default, and without those variables lerd's `npm` shim (which runs npm
68
+ through mise) finds itself on the `PATH` again and starts copies of itself until the machine runs
69
+ out of memory.
70
+
71
+ What the tests cover:
72
+
73
+ - `packages/tokens`: Vitest on the build output. The three tiers are there, semantic colours are
74
+ built from primitives in both themes, text colours meet WCAG AA, and the CSS and Tailwind output
75
+ are right (Tailwind itself compiles the theme).
76
+ - `packages/vue`: Vitest with Vue Test Utils.
77
+ - `packages/react`: Vitest with Testing Library.
78
+ - `packages/blade`: Pest with Testbench, including the Filament plugin (`composer test` there).
79
+ - `storybook`: Vitest **parity tests** render every story's markup, the Blade component, the
80
+ Vue component and the React one, and compare them ignoring whitespace and attribute order. Then the Storybook
81
+ test runner opens every story in light and dark at 390, 768 and 1440 px and checks it with axe.
82
+
83
+ Screenshot comparison is paused until the foundations settle
84
+ ([#9](https://github.com/ptbdynamic/plinth/issues/9)).
85
+
86
+ ## Tokens
87
+
88
+ The tokens are in `packages/tokens/tokens/`, in three tiers ([ADR 1](docs/adr/0001-design-tokens-in-dtcg-built-with-style-dictionary.md)):
89
+
90
+ | Tier | File | Custom properties | Used by |
91
+ | --------- | ------------------------------------------------------ | ------------------------------- | ----------------------------------- |
92
+ | Primitive | `primitives.json` | `--plinth-ref-color-neutral-50` | semantic tokens only |
93
+ | Semantic | `semantic.json`, `semantic.light.json`, `.dark.json` | `--plinth-color-surface` | components; projects override these |
94
+ | Component | `component.json`, `component.light.json`, `.dark.json` | `--plinth-header-height` | one component |
95
+
96
+ The website has a page for each under **Foundations** (colour, typography, spacing and sizes,
97
+ radii and elevation, layers and breakpoints, motion, the focus ring and component tokens), each
98
+ showing the semantic tokens and the primitives they are built from. Its Markdown is in
99
+ [`site/docs`](site/docs).
100
+
101
+ ## Installing Plinth in a Laravel project
102
+
103
+ Once a version is on Packagist:
104
+
105
+ ```bash
106
+ composer require ptbdynamic/plinth
107
+ ```
108
+
109
+ Laravel discovers the service provider, and the components are available as `<x-plinth::badge>`.
110
+ In a Filament panel, add the plugin and publish its CSS and fonts:
111
+
112
+ ```php
113
+ use BDynamic\Plinth\Filament\PlinthPlugin;
114
+
115
+ $panel->plugin(PlinthPlugin::make());
116
+ ```
117
+
118
+ ```bash
119
+ php artisan filament:assets
120
+ ```
121
+
122
+ Outside Filament, publish them with `php artisan vendor:publish --tag=plinth-assets` and link
123
+ `vendor/plinth/fonts/index.css` and `vendor/plinth/plinth.css`, or import them in the app's Vite
124
+ CSS from `vendor/ptbdynamic/plinth/packages/blade/resources/dist/`.
125
+
126
+ ## Using Plinth from a project while you work on it
127
+
128
+ Link your local checkout instead of a published version. Build Plinth first
129
+ (`npx turbo run build`) and rebuild when you change it.
130
+
131
+ **Laravel**: add a Composer path repository to the project's `composer.json`:
132
+
133
+ ```json
134
+ {
135
+ "repositories": [
136
+ { "type": "path", "url": "../plinth/packages/blade", "options": { "symlink": true } }
137
+ ],
138
+ "require": {
139
+ "ptbdynamic/plinth": "@dev"
140
+ }
141
+ }
142
+ ```
143
+
144
+ Then `composer update ptbdynamic/plinth`. In a Filament panel, add the plugin and publish its
145
+ CSS:
146
+
147
+ ```php
148
+ use BDynamic\Plinth\Filament\PlinthPlugin;
149
+
150
+ $panel->plugin(PlinthPlugin::make());
151
+ ```
152
+
153
+ ```bash
154
+ php artisan filament:assets
155
+ ```
156
+
157
+ Outside Filament, publish the CSS and fonts with `php artisan vendor:publish --tag=plinth-assets`
158
+ and link `vendor/plinth/fonts/index.css` and `vendor/plinth/plinth.css`. The Filament plugin
159
+ registers both itself. Components are then available as `<x-plinth::badge>`.
160
+
161
+ **Vue**: point npm at the checkout with a `file:` link:
162
+
163
+ ```json
164
+ {
165
+ "dependencies": {
166
+ "@ptbdynamic/plinth": "file:../plinth"
167
+ }
168
+ }
169
+ ```
170
+
171
+ ```ts
172
+ import '@ptbdynamic/plinth/fonts.css'; // Geist and Geist Mono, the default typefaces
173
+ import '@ptbdynamic/plinth/css';
174
+ import { PlinthBadge } from '@ptbdynamic/plinth/vue';
175
+ ```
176
+
177
+ **React** (an Inertia React app, or any React 19 app): the same `file:` link, then
178
+
179
+ ```tsx
180
+ import '@ptbdynamic/plinth/fonts.css';
181
+ import '@ptbdynamic/plinth/css';
182
+ import { PlinthButton } from '@ptbdynamic/plinth/react';
183
+
184
+ <PlinthButton variant="secondary" onClick={save}>
185
+ Save
186
+ </PlinthButton>;
187
+ ```
188
+
189
+ The React components take the Vue props by the same names. Vue's default slot is `children`, and
190
+ its named slots are props that take a node (`icon`, `trailing`). Everything else, `className`
191
+ and `ref` included, reaches the root element. They render the same on the server (Inertia's SSR)
192
+ and in the browser.
193
+
194
+ **Tailwind v4**: Plinth's CSS is in `@layer plinth`. Place that layer after Tailwind's reset and
195
+ before its utilities, so a utility class can adjust a component without `!important`. Then import
196
+ the theme file, so page code gets the tokens as utilities (`bg-surface`, `text-text-muted`, `p-4`,
197
+ `rounded-md`):
198
+
199
+ ```css
200
+ @layer theme, base, plinth, components, utilities;
201
+ @import 'tailwindcss';
202
+ @import '@ptbdynamic/plinth/css';
203
+ @import '@ptbdynamic/plinth/tailwind.css';
204
+ ```
205
+
206
+ **Your brand**: override the **semantic** tokens after Plinth's CSS, outside any layer (Plinth's
207
+ own are inside `@layer plinth`, so yours win). Storybook's example brand,
208
+ [`storybook/brands/example.css`](storybook/brands/example.css), is a complete example; switch to
209
+ it with **Brand** in the toolbar.
210
+
211
+ ```css
212
+ :root {
213
+ --plinth-color-accent: #0f766e;
214
+ --plinth-color-accent-hover: #115e59; /* override these two with accent */
215
+ --plinth-color-accent-active: #134e4a;
216
+ --plinth-radius-pill: 0.25rem;
217
+ --plinth-font-family-body: 'Your Font', system-ui, sans-serif;
218
+ }
219
+ [data-theme='dark'] {
220
+ --plinth-color-accent: #14b8a6;
221
+ }
222
+ ```
223
+
224
+ Dark mode follows the system, or `data-theme="dark"` / the `.dark` class on any ancestor;
225
+ `data-theme="light"` forces light.
226
+
227
+ ## Releases
228
+
229
+ Plinth has one version for npm and Packagist, and one tag, `v<version>`
230
+ ([ADR 4](docs/adr/0004-one-version-released-with-changesets.md)). What changed is in
231
+ `CHANGELOG.md` (from the first release on), written by [Changesets](https://github.com/changesets/changesets):
232
+
233
+ ```bash
234
+ npx changeset # in a pull request that changes a package: the bump and a line for the changelog
235
+ npm run release:version # on a branch: the next version, in every package and the changelog
236
+ npm run release -- --dry-run # what npm and Packagist would get, without tagging or publishing
237
+ npm run release # on main, after the version pull request merges: tag and publish
238
+ ```
239
+
240
+ The dry run (CI runs it on every pull request) also checks the Composer side: the root
241
+ `composer.json` and `packages/blade/composer.json` name the same package with the same runtime
242
+ requirements and provider and have no `version` (Packagist takes it from the tag),
243
+ `composer validate --strict` passes on the root one, and `git archive` of the tag (or of `HEAD`
244
+ before tagging) holds what the package needs and nothing else.
245
+
246
+ ### One-time steps for the owner
247
+
248
+ Both registries are set up: Packagist publishes each tag through its GitHub hook, and
249
+ `npm run release` publishes `@ptbdynamic/plinth` to npm under the `ptbdynamic` organisation
250
+ (the person releasing runs `npm login` first, with a one-time code if their account uses
251
+ two-factor authentication).
252
+
253
+ ## Contributing
254
+
255
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the rules every component follows.
256
+
257
+ ## Licence
258
+
259
+ [MIT](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,101 @@
1
1
  {
2
2
  "name": "@ptbdynamic/plinth",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "The design system of BDynamic's projects: tokens, plain HTML and CSS components, and thin Blade, Vue and React layers.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "packageManager": "npm@10.9.9",
8
+ "engines": {
9
+ "node": ">=22.18"
10
+ },
11
+ "workspaces": [
12
+ "packages/tokens",
13
+ "packages/css",
14
+ "packages/vue",
15
+ "packages/react",
16
+ "packages/blade",
17
+ "storybook",
18
+ "site"
19
+ ],
20
+ "exports": {
21
+ "./tokens": {
22
+ "types": "./packages/tokens/dist/index.d.ts",
23
+ "default": "./packages/tokens/dist/index.js"
24
+ },
25
+ "./tokens.css": "./packages/tokens/dist/tokens.css",
26
+ "./tailwind.css": "./packages/tokens/dist/tailwind-theme.css",
27
+ "./css": "./packages/css/dist/plinth.css",
28
+ "./fonts.css": "./packages/css/dist/fonts/index.css",
29
+ "./css/*": "./packages/css/dist/*",
30
+ "./vue": {
31
+ "types": "./packages/vue/dist/index.d.ts",
32
+ "default": "./packages/vue/dist/index.js"
33
+ },
34
+ "./react": {
35
+ "types": "./packages/react/dist/index.d.ts",
36
+ "default": "./packages/react/dist/index.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "packages/tokens/dist",
41
+ "packages/css/dist",
42
+ "packages/vue/dist",
43
+ "packages/react/dist"
44
+ ],
45
+ "peerDependencies": {
46
+ "vue": "^3.5.0",
47
+ "react": "^19.0.0",
48
+ "react-dom": "^19.0.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "vue": {
52
+ "optional": true
53
+ },
54
+ "react": {
55
+ "optional": true
56
+ },
57
+ "react-dom": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "scripts": {
62
+ "check": "turbo run lint test build build-storybook",
63
+ "build": "turbo run build",
64
+ "lint": "eslint --cache --cache-location node_modules/.cache/eslint . && prettier --check --cache . && tsc -p scripts",
65
+ "format": "prettier --write --cache . && stylelint --fix \"packages/css/src/**/*.css\" && composer --working-dir=packages/blade format",
66
+ "test": "turbo run test",
67
+ "storybook": "turbo run storybook",
68
+ "build-storybook": "turbo run build-storybook",
69
+ "test:scripts": "vitest run --config scripts/vitest.config.ts --coverage",
70
+ "changeset": "changeset",
71
+ "release:version": "node scripts/release/version.ts",
72
+ "release": "node scripts/release/release.ts"
73
+ },
74
+ "devDependencies": {
75
+ "@changesets/cli": "^2.31.1",
76
+ "@changesets/read": "^1.0.1",
77
+ "@eslint/js": "^10.0.1",
78
+ "@types/node": "^22.20.5",
79
+ "@vitest/coverage-v8": "^5.0.5",
80
+ "eslint": "^10.13.0",
81
+ "eslint-config-prettier": "^10.1.8",
82
+ "eslint-plugin-storybook": "^10.6.2",
83
+ "eslint-plugin-vue": "^10.11.1",
84
+ "globals": "^17.13.0",
85
+ "prettier": "^3.9.10",
86
+ "stylelint": "^17.16.0",
87
+ "stylelint-config-recess-order": "^7.8.0",
88
+ "stylelint-config-standard": "^40.0.0",
89
+ "stylelint-order": "^8.1.2",
90
+ "turbo": "^2.11.9",
91
+ "typescript": "~6.0.3",
92
+ "typescript-eslint": "^8.71.2",
93
+ "vitest": "^5.0.5"
94
+ },
95
+ "overrides": {
96
+ "jest-runtime": "30.4.2"
97
+ },
98
+ "publishConfig": {
99
+ "access": "public"
100
+ }
101
+ }
@@ -0,0 +1,122 @@
1
+ /*
2
+ * Avatar: a person or an organisation, as an image, initials, or a person glyph.
3
+ *
4
+ * <span class="plinth-avatar" data-size="md" role="img" aria-label="Ana Lima, Online">
5
+ * <img class="plinth-avatar-image" src="…" alt="">
6
+ * or <span class="plinth-avatar-initials" aria-hidden="true">AL</span>
7
+ * or <svg class="plinth-avatar-glyph" aria-hidden="true">…</svg>
8
+ * <span class="plinth-avatar-status" data-tone="success" aria-hidden="true"></span>
9
+ * </span>
10
+ *
11
+ * The root carries the name (role="img", aria-label), with the status label after a comma. A
12
+ * decorative avatar, next to the name it shows, has aria-hidden="true" and no role or label.
13
+ * Image first, then initials (from the name, or given), then the glyph.
14
+ *
15
+ * Sizes (data-size): sm 24px, md 32px (default), lg 40px, xl 56px. Round by default
16
+ * (radius.pill), so a brand can square it. The status dot takes the badge's tones (data-tone:
17
+ * neutral, success, warning, danger) and a ring in the page colour; on another surface, set
18
+ * --plinth-avatar-ring on the container (a card: --plinth-color-surface-raised).
19
+ */
20
+
21
+ @layer plinth {
22
+ .plinth-avatar {
23
+ --plinth-avatar-size: var(--plinth-size-avatar-md);
24
+ --plinth-avatar-surface: var(--plinth-color-surface-sunken);
25
+ --plinth-avatar-status: var(--plinth-color-text-subtle);
26
+ --plinth-avatar-ring: var(--plinth-color-surface);
27
+
28
+ position: relative;
29
+ display: inline-flex;
30
+ flex: none;
31
+ align-items: center;
32
+ justify-content: center;
33
+ inline-size: var(--plinth-avatar-size);
34
+ block-size: var(--plinth-avatar-size);
35
+ font-family: var(--plinth-font-family-body);
36
+ font-size: var(--plinth-font-size-xs);
37
+ font-weight: var(--plinth-font-weight-medium);
38
+ line-height: 1;
39
+ vertical-align: middle;
40
+ color: var(--plinth-color-text-muted);
41
+ user-select: none;
42
+ background-color: var(--plinth-avatar-surface);
43
+ border-radius: var(--plinth-radius-pill);
44
+ }
45
+
46
+ /*
47
+ * The hairline ring: over an image (positioned boxes paint after in-flow ones) and under the
48
+ * status dot (which comes later). A border, so forced-colours mode keeps the circle.
49
+ */
50
+ .plinth-avatar::before {
51
+ position: absolute;
52
+ inset: 0;
53
+ box-sizing: border-box;
54
+ pointer-events: none;
55
+ content: '';
56
+ border: var(--plinth-border-width-thin) solid var(--plinth-color-border);
57
+ border-radius: inherit;
58
+ }
59
+
60
+ .plinth-avatar[data-size='sm'] {
61
+ --plinth-avatar-size: var(--plinth-size-avatar-sm);
62
+ }
63
+
64
+ .plinth-avatar[data-size='lg'] {
65
+ --plinth-avatar-size: var(--plinth-size-avatar-lg);
66
+
67
+ font-size: var(--plinth-font-size-md);
68
+ }
69
+
70
+ .plinth-avatar[data-size='xl'] {
71
+ --plinth-avatar-size: var(--plinth-size-avatar-xl);
72
+
73
+ font-size: var(--plinth-font-size-xl);
74
+ }
75
+
76
+ .plinth-avatar-image {
77
+ inline-size: 100%;
78
+ block-size: 100%;
79
+ object-fit: cover;
80
+ border-radius: inherit;
81
+ }
82
+
83
+ .plinth-avatar-initials {
84
+ letter-spacing: var(--plinth-font-tracking-snug);
85
+ }
86
+
87
+ .plinth-avatar-glyph {
88
+ inline-size: 60%;
89
+ block-size: 60%;
90
+ }
91
+
92
+ .plinth-avatar-status {
93
+ position: absolute;
94
+ inset-block-end: 0;
95
+ inset-inline-end: 0;
96
+ inline-size: calc(var(--plinth-avatar-size) * 0.3);
97
+ block-size: calc(var(--plinth-avatar-size) * 0.3);
98
+ background-color: var(--plinth-avatar-status);
99
+
100
+ /* Stays round when a brand squares the avatar. */
101
+ border-radius: 50%;
102
+ box-shadow: 0 0 0 var(--plinth-border-width-thick) var(--plinth-avatar-ring);
103
+ }
104
+
105
+ .plinth-avatar-status[data-tone='success'] {
106
+ --plinth-avatar-status: var(--plinth-color-success);
107
+ }
108
+
109
+ .plinth-avatar-status[data-tone='warning'] {
110
+ --plinth-avatar-status: var(--plinth-color-warning);
111
+ }
112
+
113
+ .plinth-avatar-status[data-tone='danger'] {
114
+ --plinth-avatar-status: var(--plinth-color-danger);
115
+ }
116
+
117
+ @media (forced-colors: active) {
118
+ .plinth-avatar-status {
119
+ background-color: CanvasText;
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Badge: a short label with an optional dot and a tone.
3
+ *
4
+ * <span class="plinth-badge" data-tone="success">
5
+ * <span class="plinth-badge-dot" aria-hidden="true"></span>
6
+ * <span class="plinth-badge-label">Paid</span>
7
+ * </span>
8
+ *
9
+ * Tones: neutral (default, also when data-tone is missing), success, warning, danger.
10
+ * The label carries the meaning; the tone and the dot only reinforce it.
11
+ */
12
+
13
+ @layer plinth {
14
+ .plinth-badge {
15
+ --plinth-badge-surface: var(--plinth-color-surface-sunken);
16
+ --plinth-badge-text: var(--plinth-color-text-muted);
17
+ --plinth-badge-accent: var(--plinth-color-text-muted);
18
+
19
+ display: inline-flex;
20
+ gap: var(--plinth-space-1);
21
+ align-items: center;
22
+ min-inline-size: 0;
23
+ max-inline-size: 100%;
24
+ padding-block: var(--plinth-space-0-5);
25
+ padding-inline: var(--plinth-space-2);
26
+ font-family: var(--plinth-font-family-body);
27
+ font-size: var(--plinth-font-size-xs);
28
+ font-weight: var(--plinth-font-weight-medium);
29
+ line-height: var(--plinth-font-leading-normal);
30
+ vertical-align: middle;
31
+ color: var(--plinth-badge-text);
32
+ white-space: nowrap;
33
+ background-color: var(--plinth-badge-surface);
34
+
35
+ /* Invisible normally; drawn by the system in forced-colours mode so the badge keeps its edge. */
36
+ border: 1px solid transparent;
37
+ border-radius: var(--plinth-radius-pill);
38
+ }
39
+
40
+ .plinth-badge[data-tone='success'] {
41
+ --plinth-badge-surface: var(--plinth-color-success-surface);
42
+ --plinth-badge-text: var(--plinth-color-success-text);
43
+ --plinth-badge-accent: var(--plinth-color-success);
44
+ }
45
+
46
+ .plinth-badge[data-tone='warning'] {
47
+ --plinth-badge-surface: var(--plinth-color-warning-surface);
48
+ --plinth-badge-text: var(--plinth-color-warning-text);
49
+ --plinth-badge-accent: var(--plinth-color-warning);
50
+ }
51
+
52
+ .plinth-badge[data-tone='danger'] {
53
+ --plinth-badge-surface: var(--plinth-color-danger-surface);
54
+ --plinth-badge-text: var(--plinth-color-danger-text);
55
+ --plinth-badge-accent: var(--plinth-color-danger);
56
+ }
57
+
58
+ .plinth-badge-dot {
59
+ flex: none;
60
+ inline-size: var(--plinth-space-2);
61
+ block-size: var(--plinth-space-2);
62
+ background-color: var(--plinth-badge-accent);
63
+ border-radius: var(--plinth-radius-pill);
64
+ }
65
+
66
+ .plinth-badge-label {
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ }
70
+
71
+ @media (forced-colors: active) {
72
+ .plinth-badge-dot {
73
+ background-color: currentcolor;
74
+ }
75
+ }
76
+ }