@_linked/primitives 1.1.0 → 1.2.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/.gitattributes +1 -0
- package/.github/workflows/pr.yml +18 -0
- package/.github/workflows/publish.yml +16 -63
- package/CHANGELOG.md +61 -0
- package/README.md +62 -0
- package/lib/cjs/components/ConfirmDialog.d.ts +37 -0
- package/lib/cjs/components/ConfirmDialog.js +68 -0
- package/lib/cjs/components/ConfirmDialog.js.map +1 -0
- package/lib/cjs/components/ConfirmDialog.module.css +22 -0
- package/lib/cjs/components/ImageThumb.d.ts +17 -0
- package/lib/cjs/components/ImageThumb.js +90 -0
- package/lib/cjs/components/ImageThumb.js.map +1 -0
- package/lib/cjs/components/ImageThumb.module.css +134 -0
- package/lib/cjs/components/SkeletonLoader.d.ts +14 -0
- package/lib/cjs/components/SkeletonLoader.js +71 -0
- package/lib/cjs/components/SkeletonLoader.js.map +1 -0
- package/lib/cjs/components/SkeletonLoader.module.css +83 -0
- package/lib/cjs/components/Spinner.d.ts +22 -3
- package/lib/cjs/components/Spinner.js +55 -3
- package/lib/cjs/components/Spinner.js.map +1 -1
- package/lib/cjs/components/Spinner.module.css +57 -10
- package/lib/cjs/components/TruncatedText.d.ts +16 -0
- package/lib/cjs/components/TruncatedText.js +130 -0
- package/lib/cjs/components/TruncatedText.js.map +1 -0
- package/lib/cjs/components/TruncatedText.module.css +162 -0
- package/lib/cjs/hooks/useReducedMotion.d.ts +12 -0
- package/lib/cjs/hooks/useReducedMotion.js +35 -0
- package/lib/cjs/hooks/useReducedMotion.js.map +1 -0
- package/lib/cjs/index.d.ts +5 -0
- package/lib/cjs/index.js +21 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/motion.d.ts +219 -0
- package/lib/cjs/motion.js +154 -0
- package/lib/cjs/motion.js.map +1 -0
- package/lib/cjs/ontologies/primitives.d.ts +10 -1
- package/lib/cjs/ontologies/primitives.js +1 -1
- package/lib/cjs/ontologies/primitives.js.map +1 -1
- package/lib/cjs/package.json +12 -11
- package/lib/esm/components/ConfirmDialog.d.ts +37 -0
- package/lib/esm/components/ConfirmDialog.js +29 -0
- package/lib/esm/components/ConfirmDialog.js.map +1 -0
- package/lib/esm/components/ConfirmDialog.module.css +22 -0
- package/lib/esm/components/ImageThumb.d.ts +17 -0
- package/lib/esm/components/ImageThumb.js +50 -0
- package/lib/esm/components/ImageThumb.js.map +1 -0
- package/lib/esm/components/ImageThumb.module.css +134 -0
- package/lib/esm/components/SkeletonLoader.d.ts +14 -0
- package/lib/esm/components/SkeletonLoader.js +31 -0
- package/lib/esm/components/SkeletonLoader.js.map +1 -0
- package/lib/esm/components/SkeletonLoader.module.css +83 -0
- package/lib/esm/components/Spinner.d.ts +22 -3
- package/lib/esm/components/Spinner.js +22 -3
- package/lib/esm/components/Spinner.js.map +1 -1
- package/lib/esm/components/Spinner.module.css +57 -10
- package/lib/esm/components/TruncatedText.d.ts +16 -0
- package/lib/esm/components/TruncatedText.js +83 -0
- package/lib/esm/components/TruncatedText.js.map +1 -0
- package/lib/esm/components/TruncatedText.module.css +162 -0
- package/lib/esm/hooks/useReducedMotion.d.ts +12 -0
- package/lib/esm/hooks/useReducedMotion.js +32 -0
- package/lib/esm/hooks/useReducedMotion.js.map +1 -0
- package/lib/esm/index.d.ts +5 -0
- package/lib/esm/index.js +7 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/motion.d.ts +219 -0
- package/lib/esm/motion.js +154 -0
- package/lib/esm/motion.js.map +1 -0
- package/lib/esm/ontologies/primitives.d.ts +10 -1
- package/lib/esm/ontologies/primitives.js +3 -1
- package/lib/esm/ontologies/primitives.js.map +1 -1
- package/lib/esm/package.json +12 -11
- package/package.json +12 -11
- package/.github/workflows/changeset-check.yml +0 -44
- package/.github/workflows/ci.yml +0 -43
package/.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
package-lock.json linguist-generated=true -diff merge=ours
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: PR
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
checks:
|
|
10
|
+
uses: linked-fw/.github/.github/workflows/pr.yml@v1
|
|
11
|
+
# The changeset reminder posts a PR comment. A reusable workflow only holds what its
|
|
12
|
+
# caller granted, and the org default is read-only.
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
pull-requests: write
|
|
16
|
+
with:
|
|
17
|
+
require-tests: false
|
|
18
|
+
run-e2e: false
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Calls the shared workflow in linked-fw/.github.
|
|
2
|
+
#
|
|
3
|
+
# This FILENAME is what npm's trusted-publisher check validates — not the shared workflow's.
|
|
4
|
+
# Renaming this file invalidates this package's trusted publisher, and the only symptom is
|
|
5
|
+
# that releases quietly start needing manual approval again.
|
|
1
6
|
name: Publish
|
|
2
7
|
|
|
3
8
|
on:
|
|
@@ -9,69 +14,17 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|
|
9
14
|
|
|
10
15
|
jobs:
|
|
11
16
|
release:
|
|
12
|
-
|
|
13
|
-
runs-on: ubuntu-latest
|
|
17
|
+
uses: linked-fw/.github/.github/workflows/publish.yml@v1
|
|
14
18
|
permissions:
|
|
15
19
|
contents: write
|
|
16
20
|
pull-requests: write
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- name: Checkout
|
|
29
|
-
uses: actions/checkout@v4
|
|
30
|
-
with:
|
|
31
|
-
token: ${{ steps.app-token.outputs.token }}
|
|
32
|
-
|
|
33
|
-
- name: Setup Node.js
|
|
34
|
-
uses: actions/setup-node@v4
|
|
35
|
-
with:
|
|
36
|
-
node-version: 22.16.0
|
|
37
|
-
registry-url: 'https://registry.npmjs.org'
|
|
38
|
-
|
|
39
|
-
- name: Install dependencies
|
|
40
|
-
run: npm ci --legacy-peer-deps
|
|
41
|
-
|
|
42
|
-
- name: Build
|
|
43
|
-
run: npm run build
|
|
44
|
-
|
|
45
|
-
- name: Create release Pull Request or Publish
|
|
46
|
-
id: changesets
|
|
47
|
-
uses: changesets/action@v1
|
|
48
|
-
with:
|
|
49
|
-
publish: npx changeset publish
|
|
50
|
-
title: 'chore: release'
|
|
51
|
-
commit: 'chore: release'
|
|
52
|
-
env:
|
|
53
|
-
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
54
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
55
|
-
NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
56
|
-
|
|
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
|
|
60
|
-
# auto-merge ignores branch-protection review-bypass allowances; main is check-only so a
|
|
61
|
-
# 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'
|
|
64
|
-
env:
|
|
65
|
-
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
66
|
-
run: |
|
|
67
|
-
pr=$(gh pr list --base main --head changeset-release/main --state open --json number --jq '.[0].number // empty')
|
|
68
|
-
if [ -z "$pr" ]; then echo "No open version PR (nothing to merge)."; exit 0; fi
|
|
69
|
-
for i in $(seq 1 40); do
|
|
70
|
-
if gh pr merge "$pr" --merge 2>/tmp/merge_err; then
|
|
71
|
-
echo "Merged version PR #$pr on attempt $i."
|
|
72
|
-
exit 0
|
|
73
|
-
fi
|
|
74
|
-
echo "Attempt $i/40: version PR #$pr not mergeable yet ($(tr -d '\n' < /tmp/merge_err)); waiting for the required check…"
|
|
75
|
-
sleep 15
|
|
76
|
-
done
|
|
77
|
-
echo "::warning::Version PR #$pr not merged after retries — merge it manually."
|
|
21
|
+
# Needed HERE as well as in the shared workflow: a reusable workflow cannot widen the
|
|
22
|
+
# permissions its caller was granted, and without this npm never gets an id-token and
|
|
23
|
+
# every release silently falls back to the approval queue.
|
|
24
|
+
id-token: write
|
|
25
|
+
# Named rather than `inherit`: `inherit` forwards everything this repo can see, which is
|
|
26
|
+
# how a community repo would end up forwarding a first-party token.
|
|
27
|
+
secrets:
|
|
28
|
+
NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
29
|
+
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
|
30
|
+
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# @\_linked/primitives
|
|
2
2
|
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#24](https://github.com/linked-fw/primitives/pull/24) [`781aebc`](https://github.com/linked-fw/primitives/commit/781aebcdd83d3222acf9a00e54175687bb91b848) Thanks [@flyon](https://github.com/flyon)! - Declare npm as the package manager for this repo, convert the build scripts off `yarn`, and mark `package-lock.json` as a generated file.
|
|
8
|
+
|
|
9
|
+
## 1.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#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
|
|
14
|
+
hand-rolled confirm dialog with a composition over `AlertDialog`.
|
|
15
|
+
|
|
16
|
+
**`Spinner` is superseded.** The version here was twelve lines with one prop. The replacement
|
|
17
|
+
adds size variants, `prefers-reduced-motion` support, a configurable `aria-label`, and draws
|
|
18
|
+
the ring as an inner element so a flex parent cannot distort it into an ellipse. Three
|
|
19
|
+
differences worth knowing, none of which affects a caller that renders `<Spinner />`:
|
|
20
|
+
|
|
21
|
+
- `active={false}` now renders **nothing** rather than an element with `aria-busy="false"`.
|
|
22
|
+
A spinner in a scroll sentinel must not reserve space when idle, and `active` had no visual
|
|
23
|
+
effect before — `.active` was never defined in the CSS. (It also emitted a class literally
|
|
24
|
+
named `false`.)
|
|
25
|
+
- The ring is an inner `<span>`, so the border is no longer on the container. Consumer CSS
|
|
26
|
+
targeting the container's `border` needs updating; size still comes from the container.
|
|
27
|
+
- `--spinner-border-width` defaults to 2px rather than 4px, and the animation to 0.7s rather
|
|
28
|
+
than 1s. Every `--spinner-*` variable still works, so a consumer that themed it keeps its
|
|
29
|
+
theme.
|
|
30
|
+
|
|
31
|
+
**`ConfirmDialog` is new, and is a composition over the Radix `AlertDialog` already here.** It
|
|
32
|
+
replaces a hand-rolled `createPortal` implementation that had no focus trap, no escape
|
|
33
|
+
handling, no `aria-modal` and no focus restoration — the worst possible component to get
|
|
34
|
+
wrong, since its only job is guarding a destructive action. `tone="danger"` reads the
|
|
35
|
+
`--intent-danger-*` family rather than taking a class name, which is what the previous version
|
|
36
|
+
did by exporting its own CSS-module object.
|
|
37
|
+
|
|
38
|
+
**`SkeletonLoader`** and **`ImageThumb`** are new; neither had an equivalent here.
|
|
39
|
+
|
|
40
|
+
**`motion`** (new export) carries the animation presets — variants, transitions, and
|
|
41
|
+
`useReducedMotion`. Typed against `framer-motion` but importing nothing from it at runtime:
|
|
42
|
+
every export is a plain object, so `framer-motion` is an **optional** peer and a consumer that
|
|
43
|
+
does not animate never installs it.
|
|
44
|
+
|
|
45
|
+
The colours these bring are all expressed as `--<component>-*` variables falling back to
|
|
46
|
+
semantic tokens, per the library contract; none defines a raw colour.
|
|
47
|
+
|
|
48
|
+
## 1.1.1
|
|
49
|
+
|
|
50
|
+
### Patch Changes
|
|
51
|
+
|
|
52
|
+
- [#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.
|
|
53
|
+
|
|
54
|
+
The invariant was already held by every component in the package but written down nowhere:
|
|
55
|
+
a headless behaviour source, a CSS module reading `@_linked/css` tokens, and no knowledge of
|
|
56
|
+
data, shapes, or any particular application. The package description said only "a UI library
|
|
57
|
+
based on Radix UI", which undersells it and is not quite true — `Command` and `MultiSelect`
|
|
58
|
+
are cmdk, `Drawer` is vaul, and seven components are hand-written.
|
|
59
|
+
|
|
60
|
+
Stating it matters because the question this package keeps getting asked is "does X belong
|
|
61
|
+
here?", and without an answer components get copied instead of shared. The README gives the
|
|
62
|
+
three-question test and shows where the neighbouring layers sit.
|
|
63
|
+
|
|
3
64
|
## 1.1.0
|
|
4
65
|
|
|
5
66
|
### Minor 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,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;
|
|
@@ -0,0 +1,90 @@
|
|
|
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.InlineImageThumb = void 0;
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
41
|
+
const ClassNames_1 = require("@_linked/react/utils/ClassNames");
|
|
42
|
+
const ImageThumb_module_css_1 = __importDefault(require("./ImageThumb.module.css"));
|
|
43
|
+
const ImageThumb = ({ src, size = 36, hoverSize = 280, alt = '', className = '', clickToEnlarge = true, }) => {
|
|
44
|
+
const [hover, setHover] = (0, react_1.useState)(false);
|
|
45
|
+
const [lightbox, setLightbox] = (0, react_1.useState)(false);
|
|
46
|
+
const [hoverPos, setHoverPos] = (0, react_1.useState)({
|
|
47
|
+
top: 0,
|
|
48
|
+
left: 0,
|
|
49
|
+
});
|
|
50
|
+
const thumbRef = (0, react_1.useRef)(null);
|
|
51
|
+
const handleMouseEnter = (0, react_1.useCallback)(() => {
|
|
52
|
+
if (thumbRef.current) {
|
|
53
|
+
const rect = thumbRef.current.getBoundingClientRect();
|
|
54
|
+
// Position the hover preview to the right of the thumbnail,
|
|
55
|
+
// but clamp so it doesn't overflow the viewport
|
|
56
|
+
const left = Math.min(rect.right + 8, window.innerWidth - hoverSize - 16);
|
|
57
|
+
const top = Math.max(8, Math.min(rect.top - hoverSize / 2 + size / 2, window.innerHeight - hoverSize - 16));
|
|
58
|
+
setHoverPos({ top, left });
|
|
59
|
+
}
|
|
60
|
+
setHover(true);
|
|
61
|
+
}, [hoverSize, size]);
|
|
62
|
+
const handleClick = (0, react_1.useCallback)((e) => {
|
|
63
|
+
if (clickToEnlarge) {
|
|
64
|
+
e.stopPropagation();
|
|
65
|
+
setHover(false);
|
|
66
|
+
setLightbox(true);
|
|
67
|
+
}
|
|
68
|
+
}, [clickToEnlarge]);
|
|
69
|
+
const closeLightbox = (0, react_1.useCallback)(() => {
|
|
70
|
+
setLightbox(false);
|
|
71
|
+
}, []);
|
|
72
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
73
|
+
react_1.default.createElement("span", { className: (0, ClassNames_1.cl)(ImageThumb_module_css_1.default.root, className), onMouseEnter: handleMouseEnter, onMouseLeave: () => setHover(false) },
|
|
74
|
+
react_1.default.createElement("img", { ref: thumbRef, src: src, alt: alt, className: ImageThumb_module_css_1.default.thumb, style: { width: size, height: size }, onClick: handleClick, loading: "lazy" }),
|
|
75
|
+
hover && !lightbox && (react_1.default.createElement("div", { className: (0, ClassNames_1.cl)(ImageThumb_module_css_1.default.hoverPreview, hover && ImageThumb_module_css_1.default.visible), style: {
|
|
76
|
+
top: hoverPos.top,
|
|
77
|
+
left: hoverPos.left,
|
|
78
|
+
width: hoverSize,
|
|
79
|
+
height: hoverSize,
|
|
80
|
+
} },
|
|
81
|
+
react_1.default.createElement("img", { src: src, alt: alt, className: ImageThumb_module_css_1.default.hoverPreviewImage, style: { width: hoverSize, height: hoverSize } })))),
|
|
82
|
+
lightbox && (react_1.default.createElement("div", { className: ImageThumb_module_css_1.default.previewOverlay, onClick: closeLightbox },
|
|
83
|
+
react_1.default.createElement("div", { className: (0, ClassNames_1.cl)(ImageThumb_module_css_1.default.previewBackdrop, ImageThumb_module_css_1.default.visible) }),
|
|
84
|
+
react_1.default.createElement("img", { src: src, alt: alt, className: (0, ClassNames_1.cl)(ImageThumb_module_css_1.default.previewImage, ImageThumb_module_css_1.default.visible) })))));
|
|
85
|
+
};
|
|
86
|
+
/** Small inline thumbnail for use inside chips, badges, and dropdown options */
|
|
87
|
+
const InlineImageThumb = ({ src, alt = '', }) => (react_1.default.createElement("img", { src: src, alt: alt, className: ImageThumb_module_css_1.default.inlineThumb, loading: "lazy" }));
|
|
88
|
+
exports.InlineImageThumb = InlineImageThumb;
|
|
89
|
+
exports.default = ImageThumb;
|
|
90
|
+
//# sourceMappingURL=ImageThumb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageThumb.js","sourceRoot":"","sources":["../../../src/components/ImageThumb.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
@import './helpers/mixins.css';
|
|
2
|
+
@import '@_linked/css/package.css';
|
|
3
|
+
|
|
4
|
+
/* ============================================================================
|
|
5
|
+
ImageThumb.module.css
|
|
6
|
+
|
|
7
|
+
Thumbnail image with enlarged hover preview.
|
|
8
|
+
Uses semantic tokens from theme.css.
|
|
9
|
+
============================================================================ */
|
|
10
|
+
|
|
11
|
+
.root {
|
|
12
|
+
position: relative;
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
flex-shrink: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* === Thumbnail === */
|
|
18
|
+
.thumb {
|
|
19
|
+
object-fit: cover;
|
|
20
|
+
border-radius: var(--radius-field);
|
|
21
|
+
border: 1px solid var(--surface-border);
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
transition: box-shadow var(--transition-fast), transform var(--transition-fast);
|
|
24
|
+
background-color: var(--section-bg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.thumb:hover {
|
|
28
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
29
|
+
transform: scale(1.05);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* === Hover Preview Overlay === */
|
|
33
|
+
.previewOverlay {
|
|
34
|
+
position: fixed;
|
|
35
|
+
inset: 0;
|
|
36
|
+
z-index: 9999;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.previewBackdrop {
|
|
44
|
+
position: absolute;
|
|
45
|
+
inset: 0;
|
|
46
|
+
background: rgba(0, 0, 0, 0.4);
|
|
47
|
+
opacity: 0;
|
|
48
|
+
transition: opacity 0.2s ease;
|
|
49
|
+
pointer-events: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.previewBackdrop.visible {
|
|
53
|
+
opacity: 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.previewImage {
|
|
57
|
+
position: relative;
|
|
58
|
+
max-width: 80vw;
|
|
59
|
+
max-height: 80vh;
|
|
60
|
+
object-fit: contain;
|
|
61
|
+
border-radius: var(--radius-box);
|
|
62
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
|
|
63
|
+
z-index: 1;
|
|
64
|
+
opacity: 0;
|
|
65
|
+
transform: scale(0.92);
|
|
66
|
+
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
67
|
+
pointer-events: auto;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.previewImage.visible {
|
|
71
|
+
opacity: 1;
|
|
72
|
+
transform: scale(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* === Hover tooltip preview (positioned near thumbnail) === */
|
|
76
|
+
.hoverPreview {
|
|
77
|
+
position: fixed;
|
|
78
|
+
z-index: 10000;
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
opacity: 0;
|
|
81
|
+
transform: scale(0.95);
|
|
82
|
+
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
83
|
+
border-radius: var(--radius-box);
|
|
84
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
background-color: var(--section-bg);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.hoverPreview.visible {
|
|
90
|
+
opacity: 1;
|
|
91
|
+
transform: scale(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.hoverPreviewImage {
|
|
95
|
+
display: block;
|
|
96
|
+
object-fit: cover;
|
|
97
|
+
border-radius: var(--radius-box);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* === Dark mode adjustments === */
|
|
101
|
+
[data-theme='dark'] .thumb {
|
|
102
|
+
border-color: var(--surface-border);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
[data-theme='dark'] .previewBackdrop {
|
|
106
|
+
background: rgba(0, 0, 0, 0.6);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
[data-theme='dark'] .hoverPreview {
|
|
110
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* === Inline variant (used inside chips/badges) === */
|
|
114
|
+
.inlineThumb {
|
|
115
|
+
width: 20px;
|
|
116
|
+
height: 20px;
|
|
117
|
+
object-fit: cover;
|
|
118
|
+
border-radius: var(--radius-selector, 3px);
|
|
119
|
+
border: 1px solid var(--surface-border);
|
|
120
|
+
vertical-align: middle;
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* === Reduced motion === */
|
|
125
|
+
@media (prefers-reduced-motion: reduce) {
|
|
126
|
+
.thumb:hover {
|
|
127
|
+
transform: none;
|
|
128
|
+
}
|
|
129
|
+
.hoverPreview,
|
|
130
|
+
.previewImage,
|
|
131
|
+
.previewBackdrop {
|
|
132
|
+
transition: none;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface SkeletonLoaderProps {
|
|
3
|
+
width?: string | number;
|
|
4
|
+
height?: string | number;
|
|
5
|
+
borderRadius?: string | number;
|
|
6
|
+
className?: string;
|
|
7
|
+
lines?: number;
|
|
8
|
+
animated?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function SkeletonLoader({ width, height, borderRadius, className, lines, animated, }: SkeletonLoaderProps): React.JSX.Element;
|
|
11
|
+
export declare function TableRowSkeleton({ columns }: {
|
|
12
|
+
columns?: number;
|
|
13
|
+
}): React.JSX.Element;
|
|
14
|
+
export default SkeletonLoader;
|