@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.
- package/LICENSE +21 -0
- package/README.md +258 -2
- package/package.json +99 -4
- package/packages/css/dist/avatar.css +122 -0
- package/packages/css/dist/badge.css +76 -0
- package/packages/css/dist/button.css +273 -0
- package/packages/css/dist/callout.css +143 -0
- package/packages/css/dist/code-block.css +283 -0
- package/packages/css/dist/confirm-dialog.css +183 -0
- package/packages/css/dist/data-table.css +470 -0
- package/packages/css/dist/dialog.css +113 -0
- package/packages/css/dist/drawer.css +156 -0
- package/packages/css/dist/fonts/geist/LICENSE +93 -0
- package/packages/css/dist/fonts/geist/geist-cyrillic-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-cyrillic-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-latin-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-latin-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist/geist-vietnamese-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/LICENSE +93 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-cyrillic-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-latin-ext-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-latin-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-symbols2-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/geist-mono/geist-mono-vietnamese-wght-normal.woff2 +0 -0
- package/packages/css/dist/fonts/index.css +109 -0
- package/packages/css/dist/header.css +110 -0
- package/packages/css/dist/icon.css +51 -0
- package/packages/css/dist/list-card.css +76 -0
- package/packages/css/dist/list-row.css +164 -0
- package/packages/css/dist/menu-sheet.css +84 -0
- package/packages/css/dist/modal.css +367 -0
- package/packages/css/dist/pill-row.css +197 -0
- package/packages/css/dist/plinth.css +3773 -0
- package/packages/css/dist/section-menu.css +225 -0
- package/packages/css/dist/sheet.css +239 -0
- package/packages/css/dist/tokens.css +405 -0
- package/packages/react/dist/PlinthAvatar.d.ts +17 -0
- package/packages/react/dist/PlinthBadge.d.ts +7 -0
- package/packages/react/dist/PlinthButton.d.ts +29 -0
- package/packages/react/dist/PlinthCallout.d.ts +16 -0
- package/packages/react/dist/PlinthCodeBlock.d.ts +24 -0
- package/packages/react/dist/PlinthConfirmDialog.d.ts +38 -0
- package/packages/react/dist/PlinthDataTable.d.ts +11 -0
- package/packages/react/dist/PlinthDataTableCell.d.ts +15 -0
- package/packages/react/dist/PlinthDataTableColumn.d.ts +13 -0
- package/packages/react/dist/PlinthDataTableEmpty.d.ts +9 -0
- package/packages/react/dist/PlinthDataTableRow.d.ts +9 -0
- package/packages/react/dist/PlinthDataTableSort.d.ts +4 -0
- package/packages/react/dist/PlinthDialog.d.ts +26 -0
- package/packages/react/dist/PlinthDrawer.d.ts +29 -0
- package/packages/react/dist/PlinthHeader.d.ts +12 -0
- package/packages/react/dist/PlinthIcon.d.ts +10 -0
- package/packages/react/dist/PlinthIconButton.d.ts +26 -0
- package/packages/react/dist/PlinthListCard.d.ts +14 -0
- package/packages/react/dist/PlinthListRow.d.ts +29 -0
- package/packages/react/dist/PlinthMenuSheet.d.ts +24 -0
- package/packages/react/dist/PlinthPill.d.ts +17 -0
- package/packages/react/dist/PlinthPillRow.d.ts +13 -0
- package/packages/react/dist/PlinthSectionMenu.d.ts +11 -0
- package/packages/react/dist/PlinthSectionMenuItem.d.ts +20 -0
- package/packages/react/dist/PlinthSheet.d.ts +26 -0
- package/packages/react/dist/avatar.d.ts +12 -0
- package/packages/react/dist/button.d.ts +21 -0
- package/packages/react/dist/callout.d.ts +19 -0
- package/packages/react/dist/code-block.d.ts +16 -0
- package/packages/react/dist/confirm-dialog.d.ts +23 -0
- package/packages/react/dist/data-table.d.ts +15 -0
- package/packages/react/dist/dialog.d.ts +10 -0
- package/packages/react/dist/drawer.d.ts +20 -0
- package/packages/react/dist/heading.d.ts +5 -0
- package/packages/react/dist/icon.d.ts +5 -0
- package/packages/react/dist/index.d.ts +40 -0
- package/packages/react/dist/index.js +1127 -0
- package/packages/react/dist/list-row.d.ts +5 -0
- package/packages/react/dist/markup.d.ts +9 -0
- package/packages/react/dist/sheet.d.ts +12 -0
- package/packages/react/dist/size.d.ts +13 -0
- package/packages/react/dist/swipe.d.ts +15 -0
- package/packages/react/dist/tones.d.ts +5 -0
- package/packages/tokens/dist/index.d.ts +218 -0
- package/packages/tokens/dist/index.js +3966 -0
- package/packages/tokens/dist/tailwind-theme.css +80 -0
- package/packages/tokens/dist/tokens.css +405 -0
- package/packages/tokens/dist/tokens.json +3762 -0
- package/packages/vue/dist/PlinthAvatar.vue.d.ts +24 -0
- package/packages/vue/dist/PlinthBadge.vue.d.ts +21 -0
- package/packages/vue/dist/PlinthButton.vue.d.ts +42 -0
- package/packages/vue/dist/PlinthCallout.vue.d.ts +31 -0
- package/packages/vue/dist/PlinthCodeBlock.vue.d.ts +49 -0
- package/packages/vue/dist/PlinthConfirmDialog.vue.d.ts +51 -0
- package/packages/vue/dist/PlinthDataTable.vue.d.ts +21 -0
- package/packages/vue/dist/PlinthDataTableCell.vue.d.ts +29 -0
- package/packages/vue/dist/PlinthDataTableColumn.vue.d.ts +26 -0
- package/packages/vue/dist/PlinthDataTableEmpty.vue.d.ts +19 -0
- package/packages/vue/dist/PlinthDataTableRow.vue.d.ts +19 -0
- package/packages/vue/dist/PlinthDataTableSort.vue.d.ts +13 -0
- package/packages/vue/dist/PlinthDialog.vue.d.ts +45 -0
- package/packages/vue/dist/PlinthDrawer.vue.d.ts +48 -0
- package/packages/vue/dist/PlinthHeader.vue.d.ts +19 -0
- package/packages/vue/dist/PlinthIcon.vue.d.ts +23 -0
- package/packages/vue/dist/PlinthIconButton.vue.d.ts +37 -0
- package/packages/vue/dist/PlinthListCard.vue.d.ts +26 -0
- package/packages/vue/dist/PlinthListRow.vue.d.ts +38 -0
- package/packages/vue/dist/PlinthMenuSheet.vue.d.ts +48 -0
- package/packages/vue/dist/PlinthPill.vue.d.ts +28 -0
- package/packages/vue/dist/PlinthPillRow.vue.d.ts +22 -0
- package/packages/vue/dist/PlinthSectionMenu.vue.d.ts +22 -0
- package/packages/vue/dist/PlinthSectionMenuItem.vue.d.ts +28 -0
- package/packages/vue/dist/PlinthSheet.vue.d.ts +45 -0
- package/packages/vue/dist/avatar.d.ts +12 -0
- package/packages/vue/dist/button.d.ts +5 -0
- package/packages/vue/dist/callout.d.ts +19 -0
- package/packages/vue/dist/code-block.d.ts +16 -0
- package/packages/vue/dist/confirm-dialog.d.ts +23 -0
- package/packages/vue/dist/data-table.d.ts +15 -0
- package/packages/vue/dist/dialog.d.ts +10 -0
- package/packages/vue/dist/drawer.d.ts +20 -0
- package/packages/vue/dist/heading.d.ts +5 -0
- package/packages/vue/dist/icon.d.ts +5 -0
- package/packages/vue/dist/index.d.ts +44 -0
- package/packages/vue/dist/index.js +1439 -0
- package/packages/vue/dist/list-row.d.ts +5 -0
- package/packages/vue/dist/sheet.d.ts +12 -0
- package/packages/vue/dist/size.d.ts +13 -0
- package/packages/vue/dist/slots.d.ts +3 -0
- package/packages/vue/dist/swipe.d.ts +12 -0
- 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
|
-
#
|
|
1
|
+
# Plinth
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|