@_linked/primitives 1.0.9 → 1.2.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 (80) hide show
  1. package/.github/workflows/publish.yml +74 -7
  2. package/CHANGELOG.md +65 -0
  3. package/README.md +62 -0
  4. package/lib/cjs/components/Badge.d.ts +30 -0
  5. package/lib/cjs/components/Badge.js +64 -0
  6. package/lib/cjs/components/Badge.js.map +1 -0
  7. package/lib/cjs/components/Badge.module.css +89 -0
  8. package/lib/cjs/components/Command.d.ts +6 -6
  9. package/lib/cjs/components/ConfirmDialog.d.ts +37 -0
  10. package/lib/cjs/components/ConfirmDialog.js +68 -0
  11. package/lib/cjs/components/ConfirmDialog.js.map +1 -0
  12. package/lib/cjs/components/ConfirmDialog.module.css +22 -0
  13. package/lib/cjs/components/ImageThumb.d.ts +17 -0
  14. package/lib/cjs/components/ImageThumb.js +90 -0
  15. package/lib/cjs/components/ImageThumb.js.map +1 -0
  16. package/lib/cjs/components/ImageThumb.module.css +134 -0
  17. package/lib/cjs/components/MultiSelect.d.ts +4 -4
  18. package/lib/cjs/components/SkeletonLoader.d.ts +14 -0
  19. package/lib/cjs/components/SkeletonLoader.js +71 -0
  20. package/lib/cjs/components/SkeletonLoader.js.map +1 -0
  21. package/lib/cjs/components/SkeletonLoader.module.css +83 -0
  22. package/lib/cjs/components/Slider.js +7 -3
  23. package/lib/cjs/components/Slider.js.map +1 -1
  24. package/lib/cjs/components/Spinner.d.ts +22 -3
  25. package/lib/cjs/components/Spinner.js +55 -3
  26. package/lib/cjs/components/Spinner.js.map +1 -1
  27. package/lib/cjs/components/Spinner.module.css +57 -10
  28. package/lib/cjs/components/TruncatedText.d.ts +16 -0
  29. package/lib/cjs/components/TruncatedText.js +130 -0
  30. package/lib/cjs/components/TruncatedText.js.map +1 -0
  31. package/lib/cjs/components/TruncatedText.module.css +162 -0
  32. package/lib/cjs/hooks/useReducedMotion.d.ts +12 -0
  33. package/lib/cjs/hooks/useReducedMotion.js +35 -0
  34. package/lib/cjs/hooks/useReducedMotion.js.map +1 -0
  35. package/lib/cjs/index.d.ts +6 -0
  36. package/lib/cjs/index.js +22 -0
  37. package/lib/cjs/index.js.map +1 -1
  38. package/lib/cjs/motion.d.ts +219 -0
  39. package/lib/cjs/motion.js +154 -0
  40. package/lib/cjs/motion.js.map +1 -0
  41. package/lib/cjs/package.json +6 -6
  42. package/lib/esm/components/Badge.d.ts +30 -0
  43. package/lib/esm/components/Badge.js +13 -0
  44. package/lib/esm/components/Badge.js.map +1 -0
  45. package/lib/esm/components/Badge.module.css +89 -0
  46. package/lib/esm/components/Command.d.ts +6 -6
  47. package/lib/esm/components/ConfirmDialog.d.ts +37 -0
  48. package/lib/esm/components/ConfirmDialog.js +29 -0
  49. package/lib/esm/components/ConfirmDialog.js.map +1 -0
  50. package/lib/esm/components/ConfirmDialog.module.css +22 -0
  51. package/lib/esm/components/ImageThumb.d.ts +17 -0
  52. package/lib/esm/components/ImageThumb.js +50 -0
  53. package/lib/esm/components/ImageThumb.js.map +1 -0
  54. package/lib/esm/components/ImageThumb.module.css +134 -0
  55. package/lib/esm/components/MultiSelect.d.ts +4 -4
  56. package/lib/esm/components/SkeletonLoader.d.ts +14 -0
  57. package/lib/esm/components/SkeletonLoader.js +31 -0
  58. package/lib/esm/components/SkeletonLoader.js.map +1 -0
  59. package/lib/esm/components/SkeletonLoader.module.css +83 -0
  60. package/lib/esm/components/Slider.js +6 -2
  61. package/lib/esm/components/Slider.js.map +1 -1
  62. package/lib/esm/components/Spinner.d.ts +22 -3
  63. package/lib/esm/components/Spinner.js +22 -3
  64. package/lib/esm/components/Spinner.js.map +1 -1
  65. package/lib/esm/components/Spinner.module.css +57 -10
  66. package/lib/esm/components/TruncatedText.d.ts +16 -0
  67. package/lib/esm/components/TruncatedText.js +83 -0
  68. package/lib/esm/components/TruncatedText.js.map +1 -0
  69. package/lib/esm/components/TruncatedText.module.css +162 -0
  70. package/lib/esm/hooks/useReducedMotion.d.ts +12 -0
  71. package/lib/esm/hooks/useReducedMotion.js +32 -0
  72. package/lib/esm/hooks/useReducedMotion.js.map +1 -0
  73. package/lib/esm/index.d.ts +6 -0
  74. package/lib/esm/index.js +8 -0
  75. package/lib/esm/index.js.map +1 -1
  76. package/lib/esm/motion.d.ts +219 -0
  77. package/lib/esm/motion.js +154 -0
  78. package/lib/esm/motion.js.map +1 -0
  79. package/lib/esm/package.json +6 -6
  80. package/package.json +6 -6
@@ -14,6 +14,12 @@ jobs:
14
14
  permissions:
15
15
  contents: write
16
16
  pull-requests: write
17
+ # `npm stage publish --provenance` signs the tarball with a GitHub OIDC token, so the
18
+ # job needs to be able to mint one. This does NOT switch the package to OIDC trusted
19
+ # publishing — authentication is still the org npm token. It only lets npm attest where
20
+ # the tarball was built, which is worth having on a package a human is about to approve
21
+ # sight-unseen.
22
+ id-token: write
17
23
  steps:
18
24
  # Author the changesets "Version Packages" PR as the org release App (not the default
19
25
  # GITHUB_TOKEN) so its CI runs without a manual "Approve and run" gate. Requires the org
@@ -36,31 +42,92 @@ jobs:
36
42
  node-version: 22.16.0
37
43
  registry-url: 'https://registry.npmjs.org'
38
44
 
45
+ # `npm stage` landed in CLI 11.15.0; Node 22.16 ships npm 10.x, so the bundled one
46
+ # cannot stage. Pinned rather than `@latest` so a future npm release cannot change how
47
+ # releases behave without a commit here.
48
+ - name: Use an npm that can stage
49
+ run: npm i -g npm@^11.15.0
50
+
39
51
  - name: Install dependencies
40
52
  run: npm ci --legacy-peer-deps
41
53
 
42
54
  - name: Build
43
55
  run: npm run build
44
56
 
45
- - name: Create release Pull Request or Publish
57
+ # No `publish:` — changesets only opens/updates the version PR here. Publishing is the
58
+ # step below, because `changeset publish` shells out to `npm publish`, which a
59
+ # stage-only token cannot do (changesets/changesets#2025 is open for first-class
60
+ # support). Splitting them keeps changesets doing what it is good at — versions and
61
+ # changelogs — and leaves the registry call to us.
62
+ - name: Create release Pull Request
46
63
  id: changesets
47
64
  uses: changesets/action@v1
48
65
  with:
49
- publish: npx changeset publish
50
66
  title: 'chore: release'
51
67
  commit: 'chore: release'
52
68
  env:
53
69
  GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
70
+
71
+ # Stage the version for a human to approve.
72
+ #
73
+ # A staged version is uploaded but NOT public: a maintainer reviews it and approves with
74
+ # 2FA, from the Staged Packages tab in the npm user menu or `npm stage approve`. That is
75
+ # the point — every release keeps a proof-of-presence step even though CI is
76
+ # unattended — and it is why the org token is scoped "read and write (stage only)".
77
+ #
78
+ # Idempotent: this workflow runs on every push to main, including the merge of the
79
+ # version PR and any later commit, so it must do nothing when the version is already on
80
+ # the registry or already sitting in the queue.
81
+ - name: Stage the release for approval
82
+ if: steps.changesets.outputs.hasChangesets != 'true'
83
+ env:
54
84
  NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
55
85
  NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
86
+ run: |
87
+ set -euo pipefail
88
+ name=$(node -p "require('./package.json').name")
89
+ version=$(node -p "require('./package.json').version")
90
+ private=$(node -p "require('./package.json').private === true")
91
+ if [ "$private" = "true" ]; then
92
+ echo "$name is private — nothing to publish."; exit 0
93
+ fi
94
+
95
+ if npm view "$name@$version" version >/dev/null 2>&1; then
96
+ echo "$name@$version is already on the registry — nothing to do."
97
+ exit 0
98
+ fi
99
+
100
+ if npm stage list "$name" 2>/dev/null | grep -q "$version"; then
101
+ echo "$name@$version is already staged and waiting for approval."
102
+ exit 0
103
+ fi
104
+
105
+ echo "Staging $name@$version…"
106
+ npm stage publish --access public --provenance
107
+
108
+ {
109
+ echo "### 📦 \`$name@$version\` is staged, not published"
110
+ echo
111
+ echo "It is uploaded and held for review. **Approve it to make it public:**"
112
+ echo
113
+ echo "- **CLI:** \`npm stage approve $name@$version\`"
114
+ echo "- **Web:** [npmjs.com](https://www.npmjs.com) → your avatar → **Staged"
115
+ echo " Packages** → **Approve**. The button stays disabled until npm has"
116
+ echo " finished scanning the tarball; the page refreshes every minute."
117
+ echo
118
+ echo "Both prompt for 2FA. Until then \`npm install $name\` still resolves"
119
+ echo "\`$(npm view "$name" version 2>/dev/null || echo 'nothing')\`."
120
+ } >> "$GITHUB_STEP_SUMMARY"
121
+
122
+ echo "::notice title=Approval needed::$name@$version is staged. Run: npm stage approve $name@$version — or approve it from Staged Packages in the npm user menu."
56
123
 
57
- # When changesets opened/updated the version PR (rather than publishing), merge it so the
58
- # release ships without a manual click. App token → the merge's push re-triggers this
59
- # workflow, which then runs `changeset publish`. DIRECT-merge (not `--auto`): GitHub
124
+ # When changesets opened/updated the version PR, merge it so the release reaches the
125
+ # staging queue without a manual click. App token → the merge's push re-triggers this
126
+ # workflow, which then stages. DIRECT-merge (not `--auto`): GitHub
60
127
  # auto-merge ignores branch-protection review-bypass allowances; main is check-only so a
61
128
  # direct merge lands once the required check is green — retry until then.
62
- - name: Merge the version PR to publish
63
- if: steps.changesets.outputs.hasChangesets == 'true' && steps.changesets.outputs.published != 'true'
129
+ - name: Merge the version PR to stage the release
130
+ if: steps.changesets.outputs.hasChangesets == 'true'
64
131
  env:
65
132
  GH_TOKEN: ${{ steps.app-token.outputs.token }}
66
133
  run: |
package/CHANGELOG.md CHANGED
@@ -1,5 +1,70 @@
1
1
  # @\_linked/primitives
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#15](https://github.com/linked-cm/primitives/pull/15) [`cf860da`](https://github.com/linked-cm/primitives/commit/cf860da7a9df9462e7939fe1fcd430ab582152c0) Thanks [@flyon](https://github.com/flyon)! - Absorb four components that had been living inside an application, and replace the
8
+ hand-rolled confirm dialog with a composition over `AlertDialog`.
9
+
10
+ **`Spinner` is superseded.** The version here was twelve lines with one prop. The replacement
11
+ adds size variants, `prefers-reduced-motion` support, a configurable `aria-label`, and draws
12
+ the ring as an inner element so a flex parent cannot distort it into an ellipse. Three
13
+ differences worth knowing, none of which affects a caller that renders `<Spinner />`:
14
+
15
+ - `active={false}` now renders **nothing** rather than an element with `aria-busy="false"`.
16
+ A spinner in a scroll sentinel must not reserve space when idle, and `active` had no visual
17
+ effect before — `.active` was never defined in the CSS. (It also emitted a class literally
18
+ named `false`.)
19
+ - The ring is an inner `<span>`, so the border is no longer on the container. Consumer CSS
20
+ targeting the container's `border` needs updating; size still comes from the container.
21
+ - `--spinner-border-width` defaults to 2px rather than 4px, and the animation to 0.7s rather
22
+ than 1s. Every `--spinner-*` variable still works, so a consumer that themed it keeps its
23
+ theme.
24
+
25
+ **`ConfirmDialog` is new, and is a composition over the Radix `AlertDialog` already here.** It
26
+ replaces a hand-rolled `createPortal` implementation that had no focus trap, no escape
27
+ handling, no `aria-modal` and no focus restoration — the worst possible component to get
28
+ wrong, since its only job is guarding a destructive action. `tone="danger"` reads the
29
+ `--intent-danger-*` family rather than taking a class name, which is what the previous version
30
+ did by exporting its own CSS-module object.
31
+
32
+ **`SkeletonLoader`** and **`ImageThumb`** are new; neither had an equivalent here.
33
+
34
+ **`motion`** (new export) carries the animation presets — variants, transitions, and
35
+ `useReducedMotion`. Typed against `framer-motion` but importing nothing from it at runtime:
36
+ every export is a plain object, so `framer-motion` is an **optional** peer and a consumer that
37
+ does not animate never installs it.
38
+
39
+ The colours these bring are all expressed as `--<component>-*` variables falling back to
40
+ semantic tokens, per the library contract; none defines a raw colour.
41
+
42
+ ## 1.1.1
43
+
44
+ ### Patch Changes
45
+
46
+ - [#13](https://github.com/linked-cm/primitives/pull/13) [`d23b948`](https://github.com/linked-cm/primitives/commit/d23b948f28a2f552e9b40230cc8cec3b48336f6a) Thanks [@flyon](https://github.com/flyon)! - Add a README stating what this library is, and what belongs in it.
47
+
48
+ The invariant was already held by every component in the package but written down nowhere:
49
+ a headless behaviour source, a CSS module reading `@_linked/css` tokens, and no knowledge of
50
+ data, shapes, or any particular application. The package description said only "a UI library
51
+ based on Radix UI", which undersells it and is not quite true — `Command` and `MultiSelect`
52
+ are cmdk, `Drawer` is vaul, and seven components are hand-written.
53
+
54
+ Stating it matters because the question this package keeps getting asked is "does X belong
55
+ here?", and without an answer components get copied instead of shared. The README gives the
56
+ three-question test and shows where the neighbouring layers sit.
57
+
58
+ ## 1.1.0
59
+
60
+ ### Minor Changes
61
+
62
+ - [#11](https://github.com/linked-cm/primitives/pull/11) [`85deea9`](https://github.com/linked-cm/primitives/commit/85deea9406d9a5c655427d9302e015c334799691) Thanks [@flyon](https://github.com/flyon)! - Add the `Badge` primitive — badge, tag, chip and pill in one component, with the
63
+ variant and size vocabulary the rest of the library uses.
64
+
65
+ Also forwards `aria-label` and `aria-disabled` to the single thumb in `Slider`, so a
66
+ one-thumb slider is announced correctly by screen readers.
67
+
3
68
  ## 1.0.9
4
69
 
5
70
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # `@_linked/primitives`
2
+
3
+ A UI component library: a headless behaviour source, a CSS module, and nothing else.
4
+
5
+ ## What belongs here
6
+
7
+ Every component in this package is three things and no more:
8
+
9
+ 1. **A headless behaviour source** — [Radix UI](https://www.radix-ui.com) for most of them,
10
+ [cmdk](https://cmdk.paco.me) for `Command` and `MultiSelect`, [vaul](https://vaul.emilkowal.ski)
11
+ for `Drawer`, or hand-written where none of those has an answer (`Input`, `Textarea`,
12
+ `Heading`, `Text`, `Spinner`).
13
+ 2. **A CSS module** that reads design tokens from
14
+ [`@_linked/css`](https://www.npmjs.com/package/@_linked/css) and defines no colours,
15
+ spacing or radii of its own.
16
+ 3. **No knowledge of data, of shapes, or of any particular application.**
17
+
18
+ That third point is the one that decides whether something belongs here. Three questions,
19
+ and a component only qualifies if the answer to all three is no:
20
+
21
+ - Does it read or write data?
22
+ - Does it know what a SHACL shape is?
23
+ - Does it know the vocabulary of one specific product — statuses, verification levels,
24
+ domain-specific badge variants?
25
+
26
+ A component that needs any of those belongs a layer up. Something that renders *any* shape
27
+ belongs in the shape-driven layer; something that renders *one product's* concepts belongs
28
+ in that product.
29
+
30
+ ## Where it sits
31
+
32
+ | Layer | Package | Knows about |
33
+ |---|---|---|
34
+ | Design tokens | `@_linked/css` | nothing — CSS custom properties only |
35
+ | **Headless components** | **`@_linked/primitives`** | **the DOM and the tokens** |
36
+ | Data binding | `@_linked/react` | shapes and queries; ships no components |
37
+ | Shape-driven UI | `@_linked/ui` | how to render an arbitrary shape |
38
+ | Product surfaces | the application | its own domain vocabulary |
39
+
40
+ The layering is what keeps the same component from being written three times. When a
41
+ component has no stated home, it gets copied — and then the copies drift.
42
+
43
+ ## Theming
44
+
45
+ Components never hard-code a value. Each reads a component-scoped variable that falls back
46
+ to a semantic token:
47
+
48
+ ```css
49
+ .Root {
50
+ background: var(--dialog-content-bg, var(--modal-bg));
51
+ }
52
+ ```
53
+
54
+ So a component renders sensibly with only `@_linked/css` imported, an application re-skins
55
+ it by setting the semantic token, and a single instance can be overridden by setting the
56
+ component-scoped one. See [`@_linked/css`'s styling guide](../css/docs/styling-and-themes.md)
57
+ for the token vocabulary and the required import order.
58
+
59
+ ## Authoring
60
+
61
+ See `docs/component-authoring-guide.md` in the workspace root for slot naming, the
62
+ token-fallback pattern, and the semantic mapping table.
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Badge — a small status/metadata label (badge, tag, chip, pill).
4
+ *
5
+ * One primitive, many looks: every axis is an independent prop so surfaces can
6
+ * swap variation without new components —
7
+ *
8
+ * variant solid | soft | outline | ghost fill strength
9
+ * color neutral | primary | success | warning | danger | info
10
+ * size small | medium | large
11
+ * shape pill | rounded
12
+ * dot leading status dot in the badge color
13
+ * asChild render as the child element (a link, a button) via Radix Slot,
14
+ * so an interactive badge keeps real semantics and focus behavior
15
+ *
16
+ * Theming follows the library contract: the component consumes `--badge-*`
17
+ * variables with working fallbacks, so it renders sensibly out of the box and
18
+ * apps re-skin it from their theme without touching the component.
19
+ */
20
+ export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
21
+ asChild?: boolean;
22
+ variant?: 'solid' | 'soft' | 'outline' | 'ghost';
23
+ color?: 'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
24
+ size?: 'small' | 'medium' | 'large';
25
+ shape?: 'pill' | 'rounded';
26
+ /** Leading status dot in the badge color. */
27
+ dot?: boolean;
28
+ }
29
+ declare const Badge: React.ForwardRefExoticComponent<BadgeProps & React.RefAttributes<HTMLSpanElement>>;
30
+ export { Badge };
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __rest = (this && this.__rest) || function (s, e) {
36
+ var t = {};
37
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
38
+ t[p] = s[p];
39
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
40
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
41
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
42
+ t[p[i]] = s[p[i]];
43
+ }
44
+ return t;
45
+ };
46
+ var __importDefault = (this && this.__importDefault) || function (mod) {
47
+ return (mod && mod.__esModule) ? mod : { "default": mod };
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.Badge = void 0;
51
+ const React = __importStar(require("react"));
52
+ const Badge_module_css_1 = __importDefault(require("./Badge.module.css"));
53
+ const ClassNames_1 = require("@_linked/react/utils/ClassNames");
54
+ const react_slot_1 = require("@radix-ui/react-slot");
55
+ const Badge = React.forwardRef((_a, ref) => {
56
+ var { className, variant = 'soft', color = 'neutral', size = 'medium', shape = 'pill', dot = false, asChild = false, children } = _a, props = __rest(_a, ["className", "variant", "color", "size", "shape", "dot", "asChild", "children"]);
57
+ const Comp = asChild ? react_slot_1.Slot : 'span';
58
+ return (React.createElement(Comp, Object.assign({ className: (0, ClassNames_1.cl)(Badge_module_css_1.default.Root, Badge_module_css_1.default[variant], Badge_module_css_1.default[color], Badge_module_css_1.default[size], Badge_module_css_1.default[shape], className), ref: ref }, props), asChild ? (children) : (React.createElement(React.Fragment, null,
59
+ dot && React.createElement("span", { className: Badge_module_css_1.default.dot, "aria-hidden": true }),
60
+ children))));
61
+ });
62
+ exports.Badge = Badge;
63
+ Badge.displayName = 'Badge';
64
+ //# sourceMappingURL=Badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../src/components/Badge.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,0EAAuC;AACvC,gEAAqD;AACrD,qDAA4C;AA8B5C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAC5B,CACE,EAUC,EACD,GAAG,EACH,EAAE;QAZF,EACE,SAAS,EACT,OAAO,GAAG,MAAM,EAChB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,QAAQ,EACf,KAAK,GAAG,MAAM,EACd,GAAG,GAAG,KAAK,EACX,OAAO,GAAG,KAAK,EACf,QAAQ,OAET,EADI,KAAK,cATV,gFAUC,CADS;IAIV,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,iBAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAErC,OAAO,CACL,oBAAC,IAAI,kBACH,SAAS,EAAE,IAAA,eAAE,EACX,0BAAK,CAAC,IAAI,EACV,0BAAK,CAAC,OAAO,CAAC,EACd,0BAAK,CAAC,KAAK,CAAC,EACZ,0BAAK,CAAC,IAAI,CAAC,EACX,0BAAK,CAAC,KAAK,CAAC,EACZ,SAAS,CACV,EACD,GAAG,EAAE,GAAG,IACJ,KAAK,GAER,OAAO,CAAC,CAAC,CAAC,CACT,QAAQ,CACT,CAAC,CAAC,CAAC,CACF;QACG,GAAG,IAAI,8BAAM,SAAS,EAAE,0BAAK,CAAC,GAAG,wBAAgB;QACjD,QAAQ,CACR,CACJ,CACI,CACR,CAAC;AACJ,CAAC,CACF,CAAC;AAGO,sBAAK;AAFd,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC"}
@@ -0,0 +1,89 @@
1
+ /* Badge — consumes `--badge-*` semantic variables with working fallbacks, so it
2
+ renders out of the box and apps re-skin it from their theme (same contract as
3
+ Button/Input: the component names the tokens, the app supplies them). */
4
+
5
+ .Root {
6
+ display: inline-flex;
7
+ align-items: center;
8
+ white-space: nowrap;
9
+ vertical-align: middle;
10
+ border: solid 1px transparent;
11
+ gap: var(--badge-gap, 0.3em);
12
+ font-family: var(--badge-font-family, inherit);
13
+ font-weight: var(--badge-font-weight, 500);
14
+ line-height: var(--badge-line-height, 1.2);
15
+
16
+ /* Per-color base; every visual below derives from this one custom property,
17
+ which is what keeps the variant × color matrix to two small class sets. */
18
+ --_badge-color: var(--badge-color-neutral, #64748b);
19
+ }
20
+
21
+ /* ── color ─────────────────────────────────────────────────────────────── */
22
+ .neutral { --_badge-color: var(--badge-color-neutral, #64748b); }
23
+ .primary { --_badge-color: var(--badge-color-primary, var(--primary, #4f46e5)); }
24
+ .success { --_badge-color: var(--badge-color-success, #15803d); }
25
+ .warning { --_badge-color: var(--badge-color-warning, #b45309); }
26
+ .danger { --_badge-color: var(--badge-color-danger, #b91c1c); }
27
+ .info { --_badge-color: var(--badge-color-info, #0369a1); }
28
+
29
+ /* ── variant ───────────────────────────────────────────────────────────── */
30
+ .solid {
31
+ background: var(--_badge-color);
32
+ color: var(--badge-solid-foreground, #fff);
33
+ }
34
+ .soft {
35
+ background: color-mix(in oklch, var(--_badge-color) 12%, transparent);
36
+ color: var(--_badge-color);
37
+ }
38
+ .outline {
39
+ border-color: color-mix(in oklch, var(--_badge-color) 45%, transparent);
40
+ color: var(--_badge-color);
41
+ }
42
+ .ghost {
43
+ color: var(--_badge-color);
44
+ }
45
+
46
+ /* ── size ──────────────────────────────────────────────────────────────── */
47
+ .small {
48
+ font-size: var(--badge-font-size-sm, 0.6875rem);
49
+ padding: var(--badge-padding-y-sm, 0.0625rem) var(--badge-padding-x-sm, 0.375rem);
50
+ }
51
+ .medium {
52
+ font-size: var(--badge-font-size, 0.75rem);
53
+ padding: var(--badge-padding-y, 0.125rem) var(--badge-padding-x, 0.5rem);
54
+ }
55
+ .large {
56
+ font-size: var(--badge-font-size-lg, 0.8125rem);
57
+ padding: var(--badge-padding-y-lg, 0.1875rem) var(--badge-padding-x-lg, 0.625rem);
58
+ }
59
+
60
+ /* ── shape ─────────────────────────────────────────────────────────────── */
61
+ .pill { border-radius: var(--badge-radius-pill, 999px); }
62
+ .rounded { border-radius: var(--badge-radius, var(--control-radius, 0.375rem)); }
63
+
64
+ /* ── dot ───────────────────────────────────────────────────────────────── */
65
+ .dot {
66
+ width: 0.375em;
67
+ height: 0.375em;
68
+ border-radius: 999px;
69
+ background: currentColor;
70
+ flex: none;
71
+ }
72
+
73
+ /* Interactive badges (asChild over a link/button) get hover/focus affordance
74
+ without imposing it on static ones. */
75
+ a.Root,
76
+ button.Root {
77
+ cursor: pointer;
78
+ transition: var(--transition-fast, background-color 120ms ease);
79
+ text-decoration: none;
80
+ }
81
+ a.Root:hover,
82
+ button.Root:hover {
83
+ filter: brightness(0.95);
84
+ }
85
+ a.Root:focus-visible,
86
+ button.Root:focus-visible {
87
+ outline: 2px solid color-mix(in oklch, var(--_badge-color) 45%, transparent);
88
+ outline-offset: 1px;
89
+ }
@@ -27,7 +27,7 @@ declare const Input: React.ForwardRefExoticComponent<Omit<Omit<Pick<Pick<React.D
27
27
  ref?: React.Ref<HTMLInputElement>;
28
28
  } & {
29
29
  asChild?: boolean;
30
- }, "asChild" | "key" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "type" | "value" | "onChange"> & {
30
+ }, "asChild" | "key" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "type" | "onChange" | "value"> & {
31
31
  value?: string;
32
32
  onValueChange?: (search: string) => void;
33
33
  } & React.RefAttributes<HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -53,7 +53,7 @@ declare const Group: React.ForwardRefExoticComponent<Omit<{
53
53
  ref?: React.Ref<HTMLDivElement>;
54
54
  } & {
55
55
  asChild?: boolean;
56
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "value" | "heading"> & {
56
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "heading" | "value"> & {
57
57
  heading?: React.ReactNode;
58
58
  value?: string;
59
59
  forceMount?: boolean;
@@ -71,7 +71,7 @@ declare const Item: React.ForwardRefExoticComponent<Omit<{
71
71
  ref?: React.Ref<HTMLDivElement>;
72
72
  } & {
73
73
  asChild?: boolean;
74
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "disabled" | "value" | "onSelect"> & {
74
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "onSelect" | "disabled" | "value"> & {
75
75
  disabled?: boolean;
76
76
  onSelect?: (value: string) => void;
77
77
  value?: string;
@@ -108,7 +108,7 @@ export declare const Command: {
108
108
  ref?: React.Ref<HTMLInputElement>;
109
109
  } & {
110
110
  asChild?: boolean;
111
- }, "asChild" | "key" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "type" | "value" | "onChange"> & {
111
+ }, "asChild" | "key" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "type" | "onChange" | "value"> & {
112
112
  value?: string;
113
113
  onValueChange?: (search: string) => void;
114
114
  } & React.RefAttributes<HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -134,7 +134,7 @@ export declare const Command: {
134
134
  ref?: React.Ref<HTMLDivElement>;
135
135
  } & {
136
136
  asChild?: boolean;
137
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "value" | "heading"> & {
137
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "heading" | "value"> & {
138
138
  heading?: React.ReactNode;
139
139
  value?: string;
140
140
  forceMount?: boolean;
@@ -145,7 +145,7 @@ export declare const Command: {
145
145
  ref?: React.Ref<HTMLDivElement>;
146
146
  } & {
147
147
  asChild?: boolean;
148
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "disabled" | "value" | "onSelect"> & {
148
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "onSelect" | "disabled" | "value"> & {
149
149
  disabled?: boolean;
150
150
  onSelect?: (value: string) => void;
151
151
  value?: string;
@@ -0,0 +1,37 @@
1
+ import * as React from 'react';
2
+ export interface ConfirmDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ onConfirm: () => void;
6
+ title: string;
7
+ message: React.ReactNode;
8
+ confirmText?: string;
9
+ cancelText?: string;
10
+ /**
11
+ * `danger` for a destructive action. Not a colour — a statement about consequence, which
12
+ * is what `--intent-danger-*` exists to express.
13
+ */
14
+ tone?: 'default' | 'danger';
15
+ /** The confirm action is in flight: both buttons lock. */
16
+ isLoading?: boolean;
17
+ /** Withhold confirmation until the caller has something complete to confirm. */
18
+ confirmDisabled?: boolean;
19
+ /** Replaces the confirm label while loading. */
20
+ loadingContent?: React.ReactNode;
21
+ /** Wider layout, for multi-column content such as an impact preview. */
22
+ wide?: boolean;
23
+ className?: string;
24
+ }
25
+ /**
26
+ * "Are you sure?" — a title, a message, and two buttons.
27
+ *
28
+ * A composition over `AlertDialog` rather than its own dialog. The hand-rolled version this
29
+ * replaces built its own portal and its own backdrop, and in doing so had no focus trap, no
30
+ * escape handling, no `aria-modal` and no restoration of focus on close — a confirmation
31
+ * step that a keyboard or screen-reader user could not reliably complete, which is the worst
32
+ * possible component to get wrong, since the whole point is guarding a destructive action.
33
+ *
34
+ * `alertdialog` semantics are the right ones here, not `dialog`: it interrupts, and it must
35
+ * not be dismissed by clicking away.
36
+ */
37
+ export declare function ConfirmDialog({ open, onOpenChange, onConfirm, title, message, confirmText, cancelText, tone, isLoading, confirmDisabled, loadingContent, wide, className, }: ConfirmDialogProps): React.JSX.Element;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ConfirmDialog = ConfirmDialog;
40
+ const React = __importStar(require("react"));
41
+ const ClassNames_1 = require("@_linked/react/utils/ClassNames");
42
+ const AlertDialog_js_1 = require("./AlertDialog.js");
43
+ const Button_js_1 = require("./Button.js");
44
+ const ConfirmDialog_module_css_1 = __importDefault(require("./ConfirmDialog.module.css"));
45
+ /**
46
+ * "Are you sure?" — a title, a message, and two buttons.
47
+ *
48
+ * A composition over `AlertDialog` rather than its own dialog. The hand-rolled version this
49
+ * replaces built its own portal and its own backdrop, and in doing so had no focus trap, no
50
+ * escape handling, no `aria-modal` and no restoration of focus on close — a confirmation
51
+ * step that a keyboard or screen-reader user could not reliably complete, which is the worst
52
+ * possible component to get wrong, since the whole point is guarding a destructive action.
53
+ *
54
+ * `alertdialog` semantics are the right ones here, not `dialog`: it interrupts, and it must
55
+ * not be dismissed by clicking away.
56
+ */
57
+ function ConfirmDialog({ open, onOpenChange, onConfirm, title, message, confirmText = 'Confirm', cancelText = 'Cancel', tone = 'default', isLoading = false, confirmDisabled = false, loadingContent, wide = false, className, }) {
58
+ return (React.createElement(AlertDialog_js_1.AlertDialog.Root, { open: open, onOpenChange: onOpenChange },
59
+ React.createElement(AlertDialog_js_1.AlertDialog.Content, { className: (0, ClassNames_1.cl)(ConfirmDialog_module_css_1.default.Content, wide && ConfirmDialog_module_css_1.default.wide, className) },
60
+ React.createElement(AlertDialog_js_1.AlertDialog.Header, null,
61
+ React.createElement(AlertDialog_js_1.AlertDialog.Title, null, title),
62
+ React.createElement(AlertDialog_js_1.AlertDialog.Description, { asChild: typeof message !== 'string' }, typeof message === 'string' ? message : React.createElement("div", null, message))),
63
+ React.createElement(AlertDialog_js_1.AlertDialog.Footer, null,
64
+ React.createElement(AlertDialog_js_1.AlertDialog.Cancel, { asChild: true },
65
+ React.createElement(Button_js_1.Button, { variant: "ghost", disabled: isLoading }, cancelText)),
66
+ React.createElement(Button_js_1.Button, { className: (0, ClassNames_1.cl)(tone === 'danger' && ConfirmDialog_module_css_1.default.danger), onClick: onConfirm, disabled: isLoading || confirmDisabled }, isLoading && loadingContent ? loadingContent : confirmText)))));
67
+ }
68
+ //# sourceMappingURL=ConfirmDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../src/components/ConfirmDialog.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,sCAiDC;AA3FD,6CAA+B;AAC/B,gEAAmD;AACnD,qDAA6C;AAC7C,2CAAmC;AACnC,0FAA+C;AA0B/C;;;;;;;;;;;GAWG;AACH,SAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,KAAK,EACL,OAAO,EACP,WAAW,GAAG,SAAS,EACvB,UAAU,GAAG,QAAQ,EACrB,IAAI,GAAG,SAAS,EAChB,SAAS,GAAG,KAAK,EACjB,eAAe,GAAG,KAAK,EACvB,cAAc,EACd,IAAI,GAAG,KAAK,EACZ,SAAS,GACU;IACnB,OAAO,CACL,oBAAC,4BAAW,CAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY;QACtD,oBAAC,4BAAW,CAAC,OAAO,IAClB,SAAS,EAAE,IAAA,eAAE,EAAC,kCAAK,CAAC,OAAO,EAAE,IAAI,IAAI,kCAAK,CAAC,IAAI,EAAE,SAAS,CAAC;YAE3D,oBAAC,4BAAW,CAAC,MAAM;gBACjB,oBAAC,4BAAW,CAAC,KAAK,QAAE,KAAK,CAAqB;gBAI9C,oBAAC,4BAAW,CAAC,WAAW,IAAC,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,IAC1D,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAM,OAAO,CAAO,CACrC,CACP;YACrB,oBAAC,4BAAW,CAAC,MAAM;gBACjB,oBAAC,4BAAW,CAAC,MAAM,IAAC,OAAO;oBACzB,oBAAC,kBAAM,IAAC,OAAO,EAAC,OAAO,EAAC,QAAQ,EAAE,SAAS,IACxC,UAAU,CACJ,CACU;gBAIrB,oBAAC,kBAAM,IACL,SAAS,EAAE,IAAA,eAAE,EAAC,IAAI,KAAK,QAAQ,IAAI,kCAAK,CAAC,MAAM,CAAC,EAChD,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,SAAS,IAAI,eAAe,IAErC,SAAS,IAAI,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CACpD,CACU,CACD,CACL,CACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ @import './helpers/mixins.css';
2
+ @import '@_linked/css/package.css';
3
+
4
+ .Content {
5
+ max-width: var(--confirm-dialog-width, 32rem);
6
+ }
7
+
8
+ .wide {
9
+ max-width: var(--confirm-dialog-width-wide, 56rem);
10
+ }
11
+
12
+ /* Destructive confirmation. Reads the intent family rather than a red primitive, so an app
13
+ that restyles what "danger" looks like restyles this too. */
14
+ .danger {
15
+ background: var(--confirm-dialog-danger-bg, var(--intent-danger-icon));
16
+ border-color: var(--confirm-dialog-danger-border, var(--intent-danger-border));
17
+ color: var(--confirm-dialog-danger-text, var(--color-white));
18
+ }
19
+
20
+ .danger:hover:not(:disabled) {
21
+ background: var(--confirm-dialog-danger-bg-hover, var(--intent-danger-text));
22
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ interface ImageThumbProps {
3
+ src: string;
4
+ size?: number;
5
+ hoverSize?: number;
6
+ alt?: string;
7
+ className?: string;
8
+ /** If true, clicking opens a full-screen lightbox preview */
9
+ clickToEnlarge?: boolean;
10
+ }
11
+ declare const ImageThumb: React.FC<ImageThumbProps>;
12
+ /** Small inline thumbnail for use inside chips, badges, and dropdown options */
13
+ export declare const InlineImageThumb: React.FC<{
14
+ src: string;
15
+ alt?: string;
16
+ }>;
17
+ export default ImageThumb;