@_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
|
@@ -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,50 @@
|
|
|
1
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { cl } from '@_linked/react/utils/ClassNames';
|
|
3
|
+
import style from './ImageThumb.module.css';
|
|
4
|
+
const ImageThumb = ({ src, size = 36, hoverSize = 280, alt = '', className = '', clickToEnlarge = true, }) => {
|
|
5
|
+
const [hover, setHover] = useState(false);
|
|
6
|
+
const [lightbox, setLightbox] = useState(false);
|
|
7
|
+
const [hoverPos, setHoverPos] = useState({
|
|
8
|
+
top: 0,
|
|
9
|
+
left: 0,
|
|
10
|
+
});
|
|
11
|
+
const thumbRef = useRef(null);
|
|
12
|
+
const handleMouseEnter = useCallback(() => {
|
|
13
|
+
if (thumbRef.current) {
|
|
14
|
+
const rect = thumbRef.current.getBoundingClientRect();
|
|
15
|
+
// Position the hover preview to the right of the thumbnail,
|
|
16
|
+
// but clamp so it doesn't overflow the viewport
|
|
17
|
+
const left = Math.min(rect.right + 8, window.innerWidth - hoverSize - 16);
|
|
18
|
+
const top = Math.max(8, Math.min(rect.top - hoverSize / 2 + size / 2, window.innerHeight - hoverSize - 16));
|
|
19
|
+
setHoverPos({ top, left });
|
|
20
|
+
}
|
|
21
|
+
setHover(true);
|
|
22
|
+
}, [hoverSize, size]);
|
|
23
|
+
const handleClick = useCallback((e) => {
|
|
24
|
+
if (clickToEnlarge) {
|
|
25
|
+
e.stopPropagation();
|
|
26
|
+
setHover(false);
|
|
27
|
+
setLightbox(true);
|
|
28
|
+
}
|
|
29
|
+
}, [clickToEnlarge]);
|
|
30
|
+
const closeLightbox = useCallback(() => {
|
|
31
|
+
setLightbox(false);
|
|
32
|
+
}, []);
|
|
33
|
+
return (React.createElement(React.Fragment, null,
|
|
34
|
+
React.createElement("span", { className: cl(style.root, className), onMouseEnter: handleMouseEnter, onMouseLeave: () => setHover(false) },
|
|
35
|
+
React.createElement("img", { ref: thumbRef, src: src, alt: alt, className: style.thumb, style: { width: size, height: size }, onClick: handleClick, loading: "lazy" }),
|
|
36
|
+
hover && !lightbox && (React.createElement("div", { className: cl(style.hoverPreview, hover && style.visible), style: {
|
|
37
|
+
top: hoverPos.top,
|
|
38
|
+
left: hoverPos.left,
|
|
39
|
+
width: hoverSize,
|
|
40
|
+
height: hoverSize,
|
|
41
|
+
} },
|
|
42
|
+
React.createElement("img", { src: src, alt: alt, className: style.hoverPreviewImage, style: { width: hoverSize, height: hoverSize } })))),
|
|
43
|
+
lightbox && (React.createElement("div", { className: style.previewOverlay, onClick: closeLightbox },
|
|
44
|
+
React.createElement("div", { className: cl(style.previewBackdrop, style.visible) }),
|
|
45
|
+
React.createElement("img", { src: src, alt: alt, className: cl(style.previewImage, style.visible) })))));
|
|
46
|
+
};
|
|
47
|
+
/** Small inline thumbnail for use inside chips, badges, and dropdown options */
|
|
48
|
+
export const InlineImageThumb = ({ src, alt = '', }) => (React.createElement("img", { src: src, alt: alt, className: style.inlineThumb, loading: "lazy" }));
|
|
49
|
+
export default ImageThumb;
|
|
50
|
+
//# 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;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import style from './SkeletonLoader.module.css';
|
|
3
|
+
export function SkeletonLoader({ width = '100%', height = 18, borderRadius = 4, className = '', lines = 1, animated = true, }) {
|
|
4
|
+
if (lines === 1) {
|
|
5
|
+
return (React.createElement("div", { className: `${style.skeleton} ${animated ? style.animated : ''} ${className}`, style: {
|
|
6
|
+
width,
|
|
7
|
+
height,
|
|
8
|
+
borderRadius,
|
|
9
|
+
} }));
|
|
10
|
+
}
|
|
11
|
+
return (React.createElement("div", { className: className }, Array.from({ length: lines }, (_, index) => (React.createElement("div", { key: index, className: `${style.skeleton} ${animated ? style.animated : ''} ${index < lines - 1 ? style.marginBottom : ''}`, style: {
|
|
12
|
+
width: index === lines - 1 ? '60%' : '100%',
|
|
13
|
+
height,
|
|
14
|
+
borderRadius,
|
|
15
|
+
} })))));
|
|
16
|
+
}
|
|
17
|
+
// Table row skeleton component
|
|
18
|
+
export function TableRowSkeleton({ columns = 5 }) {
|
|
19
|
+
return (React.createElement("tr", { className: style.tableRow },
|
|
20
|
+
React.createElement("td", { className: style.tableCell, style: { width: '48px' } },
|
|
21
|
+
React.createElement(SkeletonLoader, { width: 18, height: 18, borderRadius: 6 })),
|
|
22
|
+
Array.from({ length: columns }, (_, index) => (React.createElement("td", { key: index, className: style.tableCell },
|
|
23
|
+
React.createElement(SkeletonLoader, { width: "18vw", height: 18 })))),
|
|
24
|
+
React.createElement("td", { className: style.tableCell },
|
|
25
|
+
React.createElement("div", { className: style.actionButtons },
|
|
26
|
+
React.createElement(SkeletonLoader, { width: 22, height: 18, borderRadius: 6 }),
|
|
27
|
+
React.createElement(SkeletonLoader, { width: 22, height: 18, borderRadius: 6 }),
|
|
28
|
+
React.createElement(SkeletonLoader, { width: 22, height: 18, borderRadius: 6 })))));
|
|
29
|
+
}
|
|
30
|
+
export default SkeletonLoader;
|
|
31
|
+
//# sourceMappingURL=SkeletonLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkeletonLoader.js","sourceRoot":"","sources":["../../../src/components/SkeletonLoader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,MAAM,6BAA6B,CAAC;AAWhD,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,GAAG,MAAM,EACd,MAAM,GAAG,EAAE,EACX,YAAY,GAAG,CAAC,EAChB,SAAS,GAAG,EAAE,EACd,KAAK,GAAG,CAAC,EACT,QAAQ,GAAG,IAAI,GACK;IACpB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,OAAO,CACL,6BACE,SAAS,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,SAAS,EAAE,EAC7E,KAAK,EAAE;gBACL,KAAK;gBACL,MAAM;gBACN,YAAY;aACb,GACD,CACH,CAAC;IACJ,CAAC;IAED,OAAO,CACL,6BAAK,SAAS,EAAE,SAAS,IACtB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAC3C,6BACE,GAAG,EAAE,KAAK,EACV,SAAS,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,EAC/G,KAAK,EAAE;YACL,KAAK,EAAE,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YAC3C,MAAM;YACN,YAAY;SACb,GACD,CACH,CAAC,CACE,CACP,CAAC;AACJ,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,gBAAgB,CAAC,EAAE,OAAO,GAAG,CAAC,EAAwB;IACpE,OAAO,CACL,4BAAI,SAAS,EAAE,KAAK,CAAC,QAAQ;QAC3B,4BAAI,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;YACtD,oBAAC,cAAc,IAAC,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,GAAI,CACvD;QACJ,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAC7C,4BAAI,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS;YACxC,oBAAC,cAAc,IAAC,KAAK,EAAC,MAAM,EAAC,MAAM,EAAE,EAAE,GAAI,CACxC,CACN,CAAC;QACF,4BAAI,SAAS,EAAE,KAAK,CAAC,SAAS;YAC5B,6BAAK,SAAS,EAAE,KAAK,CAAC,aAAa;gBACjC,oBAAC,cAAc,IAAC,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,GAAI;gBAC1D,oBAAC,cAAc,IAAC,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,GAAI;gBAC1D,oBAAC,cAAc,IAAC,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,GAAI,CACtD,CACH,CACF,CACN,CAAC;AACJ,CAAC;AAED,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
@import './helpers/mixins.css';
|
|
2
|
+
@import '@_linked/css/package.css';
|
|
3
|
+
|
|
4
|
+
/* ============================================================================
|
|
5
|
+
SkeletonLoader.module.css
|
|
6
|
+
|
|
7
|
+
Loading placeholder with shimmer animation.
|
|
8
|
+
Uses semantic tokens from theme.css.
|
|
9
|
+
============================================================================ */
|
|
10
|
+
|
|
11
|
+
/* === Base Skeleton === */
|
|
12
|
+
.skeleton {
|
|
13
|
+
background: linear-gradient(
|
|
14
|
+
90deg,
|
|
15
|
+
var(--skeleton-bg, var(--color-gray-100)) 25%,
|
|
16
|
+
var(--skeleton-highlight, var(--color-gray-200)) 50%,
|
|
17
|
+
var(--skeleton-bg, var(--color-gray-100)) 75%
|
|
18
|
+
);
|
|
19
|
+
background-size: 200% 100%;
|
|
20
|
+
animation: shimmer 1.5s infinite;
|
|
21
|
+
display: inline-block;
|
|
22
|
+
border-radius: var(--radius-selector, 4px);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* === Animation Variant === */
|
|
26
|
+
.animated {
|
|
27
|
+
animation: shimmer 1.5s infinite;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* === Spacing === */
|
|
31
|
+
.marginBottom {
|
|
32
|
+
margin-bottom: 0.5rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* === Table Row Skeleton === */
|
|
36
|
+
.tableRow {
|
|
37
|
+
border-bottom: 1px solid var(--skeleton-row-border, var(--table-row-border));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* === Table Cell Skeleton === */
|
|
41
|
+
.tableCell {
|
|
42
|
+
padding: var(--skeleton-row-padding-y, var(--surface-gap)) var(--skeleton-row-padding-x, var(--space-sm));
|
|
43
|
+
vertical-align: middle;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* === Action Buttons Container === */
|
|
47
|
+
.actionButtons {
|
|
48
|
+
display: flex;
|
|
49
|
+
gap: var(--space-sm);
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: flex-end;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ============================================================================
|
|
55
|
+
Dark Mode
|
|
56
|
+
============================================================================ */
|
|
57
|
+
|
|
58
|
+
[data-theme='dark'] .skeleton {
|
|
59
|
+
background: linear-gradient(
|
|
60
|
+
90deg,
|
|
61
|
+
var(--skeleton-bg, var(--color-gray-800)) 25%,
|
|
62
|
+
var(--skeleton-highlight, var(--color-gray-700)) 50%,
|
|
63
|
+
var(--skeleton-bg, var(--color-gray-800)) 75%
|
|
64
|
+
);
|
|
65
|
+
background-size: 200% 100%;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[data-theme='dark'] .tableRow {
|
|
69
|
+
border-bottom-color: var(--skeleton-row-border, var(--table-row-border));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ============================================================================
|
|
73
|
+
Shimmer Animation
|
|
74
|
+
============================================================================ */
|
|
75
|
+
|
|
76
|
+
@keyframes shimmer {
|
|
77
|
+
0% {
|
|
78
|
+
background-position: -200% 0;
|
|
79
|
+
}
|
|
80
|
+
100% {
|
|
81
|
+
background-position: 200% 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
export
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface SpinnerProps {
|
|
3
|
+
/** Render nothing when false. */
|
|
3
4
|
active?: boolean;
|
|
4
|
-
|
|
5
|
+
size?: 'small' | 'medium' | 'large' | 'xlarge';
|
|
6
|
+
className?: string;
|
|
7
|
+
/** Announced to assistive technology. */
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A loading indicator: a ring that inherits its colour, so it reads on any background.
|
|
12
|
+
*
|
|
13
|
+
* Two things it does that a bare CSS spin does not. It honours
|
|
14
|
+
* `prefers-reduced-motion` — a spinner is the most common thing to make someone with
|
|
15
|
+
* vestibular sensitivity look away — showing a still ring rather than nothing, so the
|
|
16
|
+
* "busy" signal survives. And it renders **nothing** when inactive, so it can be dropped
|
|
17
|
+
* into a scroll sentinel or a button without reserving space it is not using.
|
|
18
|
+
*
|
|
19
|
+
* The ring is an inner element rather than a border on the container. A border on the
|
|
20
|
+
* container is distorted the moment a flex parent stretches it; an explicitly sized inner
|
|
21
|
+
* element stays circular.
|
|
22
|
+
*/
|
|
23
|
+
export declare function Spinner({ active, size, className, label, }: SpinnerProps): React.JSX.Element;
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import * as React from 'react';
|
|
2
2
|
import style from './Spinner.module.css';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { cl } from '@_linked/react/utils/ClassNames';
|
|
4
|
+
import { useReducedMotion } from '../hooks/useReducedMotion.js';
|
|
5
|
+
/**
|
|
6
|
+
* A loading indicator: a ring that inherits its colour, so it reads on any background.
|
|
7
|
+
*
|
|
8
|
+
* Two things it does that a bare CSS spin does not. It honours
|
|
9
|
+
* `prefers-reduced-motion` — a spinner is the most common thing to make someone with
|
|
10
|
+
* vestibular sensitivity look away — showing a still ring rather than nothing, so the
|
|
11
|
+
* "busy" signal survives. And it renders **nothing** when inactive, so it can be dropped
|
|
12
|
+
* into a scroll sentinel or a button without reserving space it is not using.
|
|
13
|
+
*
|
|
14
|
+
* The ring is an inner element rather than a border on the container. A border on the
|
|
15
|
+
* container is distorted the moment a flex parent stretches it; an explicitly sized inner
|
|
16
|
+
* element stays circular.
|
|
17
|
+
*/
|
|
18
|
+
export function Spinner({ active = true, size = 'small', className, label = 'Loading', }) {
|
|
19
|
+
const reducedMotion = useReducedMotion();
|
|
20
|
+
if (!active)
|
|
21
|
+
return null;
|
|
22
|
+
return (React.createElement("div", { className: cl(style.spinner, style[size], className), role: "progressbar", "aria-busy": "true", "aria-label": label },
|
|
23
|
+
React.createElement("span", { className: cl(style.ring, reducedMotion && style.reducedMotion), "aria-hidden": "true" })));
|
|
5
24
|
}
|
|
6
25
|
//# sourceMappingURL=Spinner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Spinner.js","sourceRoot":"","sources":["../../../src/components/Spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Spinner.js","sourceRoot":"","sources":["../../../src/components/Spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,MAAM,sBAAsB,CAAC;AACzC,OAAO,EAAC,EAAE,EAAC,MAAM,iCAAiC,CAAC;AACnD,OAAO,EAAC,gBAAgB,EAAC,MAAM,8BAA8B,CAAC;AAW9D;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,OAAO,CAAC,EACtB,MAAM,GAAG,IAAI,EACb,IAAI,GAAG,OAAO,EACd,SAAS,EACT,KAAK,GAAG,SAAS,GACJ;IACb,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IAEzC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,CACL,6BACE,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACpD,IAAI,EAAC,aAAa,eACR,MAAM,gBACJ,KAAK;QAEjB,8BACE,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,IAAI,KAAK,CAAC,aAAa,CAAC,iBACnD,MAAM,GAClB,CACE,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -2,20 +2,67 @@
|
|
|
2
2
|
@import '@_linked/css/package.css';
|
|
3
3
|
|
|
4
4
|
.spinner {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
/* Never grow or shrink in a flex parent — that would distort the ring. */
|
|
7
|
+
flex: 0 0 auto;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
color: var(--spinner-indicator-color, var(--meter-indicator-bg, currentColor));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Sized explicitly rather than filling the container, so the ring stays a circle even when
|
|
14
|
+
a flex parent stretches the box around it. */
|
|
15
|
+
.ring {
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
flex: 0 0 auto;
|
|
9
18
|
width: var(--spinner-size, --spacing(4));
|
|
10
19
|
height: var(--spinner-size, --spacing(4));
|
|
11
|
-
|
|
20
|
+
border-radius: 50%;
|
|
21
|
+
border: var(--spinner-border-width, 2px) solid currentColor;
|
|
22
|
+
border-top-color: var(--spinner-track-color, transparent);
|
|
23
|
+
opacity: 0.85;
|
|
24
|
+
animation: spinnerRotate var(--spinner-animation-duration, 0.7s) linear infinite;
|
|
12
25
|
}
|
|
13
26
|
|
|
14
|
-
@keyframes
|
|
15
|
-
|
|
16
|
-
transform: rotate(0deg);
|
|
17
|
-
}
|
|
18
|
-
100% {
|
|
27
|
+
@keyframes spinnerRotate {
|
|
28
|
+
to {
|
|
19
29
|
transform: rotate(360deg);
|
|
20
30
|
}
|
|
21
31
|
}
|
|
32
|
+
|
|
33
|
+
/* Size variants. Each sets `--spinner-size`, so a consumer that sets the variable directly
|
|
34
|
+
still wins — the variants are a shorthand for it, not a replacement. */
|
|
35
|
+
.small {
|
|
36
|
+
--spinner-size: --spacing(4);
|
|
37
|
+
}
|
|
38
|
+
.medium {
|
|
39
|
+
--spinner-size: --spacing(6);
|
|
40
|
+
}
|
|
41
|
+
.large {
|
|
42
|
+
--spinner-size: --spacing(8);
|
|
43
|
+
--spinner-border-width: 3px;
|
|
44
|
+
}
|
|
45
|
+
.xlarge {
|
|
46
|
+
--spinner-size: --spacing(12);
|
|
47
|
+
--spinner-border-width: 3px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.small,
|
|
51
|
+
.medium,
|
|
52
|
+
.large,
|
|
53
|
+
.xlarge {
|
|
54
|
+
width: var(--spinner-size);
|
|
55
|
+
height: var(--spinner-size);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Reduced motion: freeze the ring, keep it visible. Removing it entirely would remove the
|
|
59
|
+
only signal that something is loading. */
|
|
60
|
+
.reducedMotion {
|
|
61
|
+
animation: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@media (prefers-reduced-motion: reduce) {
|
|
65
|
+
.ring {
|
|
66
|
+
animation: none;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface TruncatedTextProps {
|
|
3
|
+
/** The full text content */
|
|
4
|
+
text: string;
|
|
5
|
+
/** Maximum number of characters to show before truncating (default: 80) */
|
|
6
|
+
maxLength?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* TruncatedText - Displays truncated text with hover/tap to show full content
|
|
10
|
+
*
|
|
11
|
+
* For long text fields, shows first N characters with ellipsis.
|
|
12
|
+
* On desktop: hover displays full text in a popover with copy button.
|
|
13
|
+
* On mobile: tap opens a dialog with full text and copy button.
|
|
14
|
+
*/
|
|
15
|
+
declare function TruncatedText({ text, maxLength }: TruncatedTextProps): React.JSX.Element;
|
|
16
|
+
export { TruncatedText };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Dialog } from './Dialog.js';
|
|
2
|
+
import { HoverCard } from './HoverCard.js';
|
|
3
|
+
import { IconButton } from './IconButton.js';
|
|
4
|
+
import React, { useState, useEffect } from 'react';
|
|
5
|
+
import style from './TruncatedText.module.css';
|
|
6
|
+
/**
|
|
7
|
+
* Two inline glyphs rather than a dependency on an icon set.
|
|
8
|
+
*
|
|
9
|
+
* This component needs exactly two, and a library whose contract is "a behaviour source plus
|
|
10
|
+
* a stylesheet" should not make every consumer install an icon package to draw a tick. They
|
|
11
|
+
* inherit `currentColor`, so they follow the button they sit in.
|
|
12
|
+
*/
|
|
13
|
+
const CheckGlyph = () => (React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
|
|
14
|
+
React.createElement("polyline", { points: "20 6 9 17 4 12" })));
|
|
15
|
+
const CopyGlyph = () => (React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
|
|
16
|
+
React.createElement("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
17
|
+
React.createElement("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })));
|
|
18
|
+
/**
|
|
19
|
+
* TruncatedText - Displays truncated text with hover/tap to show full content
|
|
20
|
+
*
|
|
21
|
+
* For long text fields, shows first N characters with ellipsis.
|
|
22
|
+
* On desktop: hover displays full text in a popover with copy button.
|
|
23
|
+
* On mobile: tap opens a dialog with full text and copy button.
|
|
24
|
+
*/
|
|
25
|
+
function TruncatedText({ text, maxLength = 80 }) {
|
|
26
|
+
const [copied, setCopied] = useState(false);
|
|
27
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
// Check for touch device or small screen
|
|
30
|
+
const checkMobile = () => {
|
|
31
|
+
const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
32
|
+
const isSmallScreen = window.matchMedia('(max-width: 768px)').matches;
|
|
33
|
+
setIsMobile(hasTouchScreen || isSmallScreen);
|
|
34
|
+
};
|
|
35
|
+
checkMobile();
|
|
36
|
+
window.addEventListener('resize', checkMobile);
|
|
37
|
+
return () => window.removeEventListener('resize', checkMobile);
|
|
38
|
+
}, []);
|
|
39
|
+
// If text is short enough, just render it directly
|
|
40
|
+
if (!text || text.length <= maxLength) {
|
|
41
|
+
return React.createElement("span", null, text || '-');
|
|
42
|
+
}
|
|
43
|
+
const truncatedText = text.slice(0, maxLength) + '...';
|
|
44
|
+
const handleCopy = async (e) => {
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
try {
|
|
47
|
+
await navigator.clipboard.writeText(text);
|
|
48
|
+
setCopied(true);
|
|
49
|
+
setTimeout(() => setCopied(false), 2000);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error('Failed to copy text:', err);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// Mobile: Use Dialog triggered by tap
|
|
56
|
+
if (isMobile) {
|
|
57
|
+
return (React.createElement(Dialog.Root, null,
|
|
58
|
+
React.createElement(Dialog.Trigger, { asChild: true },
|
|
59
|
+
React.createElement("span", { className: style.truncatedText }, truncatedText)),
|
|
60
|
+
React.createElement(Dialog.Content, { className: style.dialogContent },
|
|
61
|
+
React.createElement(Dialog.Header, null,
|
|
62
|
+
React.createElement(Dialog.Title, null, "Full Text")),
|
|
63
|
+
React.createElement("div", { className: style.dialogBody },
|
|
64
|
+
React.createElement("div", { className: style.scrollArea },
|
|
65
|
+
React.createElement("p", { className: style.fullText }, text))),
|
|
66
|
+
React.createElement("div", { className: style.dialogFooter },
|
|
67
|
+
React.createElement(IconButton, { size: "small", variant: "ghost", className: style.copyButton, onClick: handleCopy, "aria-label": copied ? 'Copied!' : 'Copy to clipboard' }, copied ? (React.createElement(CheckGlyph, null)) : (React.createElement(CopyGlyph, null)))))));
|
|
68
|
+
}
|
|
69
|
+
// Desktop: Use HoverCard triggered by hover
|
|
70
|
+
return (React.createElement(HoverCard.Root, { openDelay: 300, closeDelay: 200 },
|
|
71
|
+
React.createElement(HoverCard.Trigger, { asChild: true },
|
|
72
|
+
React.createElement("span", { className: style.truncatedText }, truncatedText)),
|
|
73
|
+
React.createElement(HoverCard.Portal, null,
|
|
74
|
+
React.createElement(HoverCard.Content, { side: "top", align: "start", sideOffset: 8, className: style.hoverContent },
|
|
75
|
+
React.createElement("div", { className: style.contentWrapper },
|
|
76
|
+
React.createElement("div", { className: style.scrollArea },
|
|
77
|
+
React.createElement("p", { className: style.fullText }, text)),
|
|
78
|
+
React.createElement("div", { className: style.copyButtonWrapper },
|
|
79
|
+
React.createElement(IconButton, { size: "small", variant: "ghost", className: style.copyButton, onClick: handleCopy, "aria-label": copied ? 'Copied!' : 'Copy to clipboard' }, copied ? (React.createElement(CheckGlyph, null)) : (React.createElement(CopyGlyph, null))))),
|
|
80
|
+
React.createElement(HoverCard.Arrow, { className: style.arrow })))));
|
|
81
|
+
}
|
|
82
|
+
export { TruncatedText };
|
|
83
|
+
//# sourceMappingURL=TruncatedText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TruncatedText.js","sourceRoot":"","sources":["../../../src/components/TruncatedText.tsx"],"names":[],"mappings":"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"}
|