@_linked/primitives 1.1.0 → 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 (64) hide show
  1. package/.github/workflows/publish.yml +74 -7
  2. package/CHANGELOG.md +55 -0
  3. package/README.md +62 -0
  4. package/lib/cjs/components/ConfirmDialog.d.ts +37 -0
  5. package/lib/cjs/components/ConfirmDialog.js +68 -0
  6. package/lib/cjs/components/ConfirmDialog.js.map +1 -0
  7. package/lib/cjs/components/ConfirmDialog.module.css +22 -0
  8. package/lib/cjs/components/ImageThumb.d.ts +17 -0
  9. package/lib/cjs/components/ImageThumb.js +90 -0
  10. package/lib/cjs/components/ImageThumb.js.map +1 -0
  11. package/lib/cjs/components/ImageThumb.module.css +134 -0
  12. package/lib/cjs/components/SkeletonLoader.d.ts +14 -0
  13. package/lib/cjs/components/SkeletonLoader.js +71 -0
  14. package/lib/cjs/components/SkeletonLoader.js.map +1 -0
  15. package/lib/cjs/components/SkeletonLoader.module.css +83 -0
  16. package/lib/cjs/components/Spinner.d.ts +22 -3
  17. package/lib/cjs/components/Spinner.js +55 -3
  18. package/lib/cjs/components/Spinner.js.map +1 -1
  19. package/lib/cjs/components/Spinner.module.css +57 -10
  20. package/lib/cjs/components/TruncatedText.d.ts +16 -0
  21. package/lib/cjs/components/TruncatedText.js +130 -0
  22. package/lib/cjs/components/TruncatedText.js.map +1 -0
  23. package/lib/cjs/components/TruncatedText.module.css +162 -0
  24. package/lib/cjs/hooks/useReducedMotion.d.ts +12 -0
  25. package/lib/cjs/hooks/useReducedMotion.js +35 -0
  26. package/lib/cjs/hooks/useReducedMotion.js.map +1 -0
  27. package/lib/cjs/index.d.ts +5 -0
  28. package/lib/cjs/index.js +21 -0
  29. package/lib/cjs/index.js.map +1 -1
  30. package/lib/cjs/motion.d.ts +219 -0
  31. package/lib/cjs/motion.js +154 -0
  32. package/lib/cjs/motion.js.map +1 -0
  33. package/lib/cjs/package.json +6 -6
  34. package/lib/esm/components/ConfirmDialog.d.ts +37 -0
  35. package/lib/esm/components/ConfirmDialog.js +29 -0
  36. package/lib/esm/components/ConfirmDialog.js.map +1 -0
  37. package/lib/esm/components/ConfirmDialog.module.css +22 -0
  38. package/lib/esm/components/ImageThumb.d.ts +17 -0
  39. package/lib/esm/components/ImageThumb.js +50 -0
  40. package/lib/esm/components/ImageThumb.js.map +1 -0
  41. package/lib/esm/components/ImageThumb.module.css +134 -0
  42. package/lib/esm/components/SkeletonLoader.d.ts +14 -0
  43. package/lib/esm/components/SkeletonLoader.js +31 -0
  44. package/lib/esm/components/SkeletonLoader.js.map +1 -0
  45. package/lib/esm/components/SkeletonLoader.module.css +83 -0
  46. package/lib/esm/components/Spinner.d.ts +22 -3
  47. package/lib/esm/components/Spinner.js +22 -3
  48. package/lib/esm/components/Spinner.js.map +1 -1
  49. package/lib/esm/components/Spinner.module.css +57 -10
  50. package/lib/esm/components/TruncatedText.d.ts +16 -0
  51. package/lib/esm/components/TruncatedText.js +83 -0
  52. package/lib/esm/components/TruncatedText.js.map +1 -0
  53. package/lib/esm/components/TruncatedText.module.css +162 -0
  54. package/lib/esm/hooks/useReducedMotion.d.ts +12 -0
  55. package/lib/esm/hooks/useReducedMotion.js +32 -0
  56. package/lib/esm/hooks/useReducedMotion.js.map +1 -0
  57. package/lib/esm/index.d.ts +5 -0
  58. package/lib/esm/index.js +7 -0
  59. package/lib/esm/index.js.map +1 -1
  60. package/lib/esm/motion.d.ts +219 -0
  61. package/lib/esm/motion.js +154 -0
  62. package/lib/esm/motion.js.map +1 -0
  63. package/lib/esm/package.json +6 -6
  64. package/package.json +6 -6
@@ -0,0 +1,71 @@
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.SkeletonLoader = SkeletonLoader;
40
+ exports.TableRowSkeleton = TableRowSkeleton;
41
+ const React = __importStar(require("react"));
42
+ const SkeletonLoader_module_css_1 = __importDefault(require("./SkeletonLoader.module.css"));
43
+ function SkeletonLoader({ width = '100%', height = 18, borderRadius = 4, className = '', lines = 1, animated = true, }) {
44
+ if (lines === 1) {
45
+ return (React.createElement("div", { className: `${SkeletonLoader_module_css_1.default.skeleton} ${animated ? SkeletonLoader_module_css_1.default.animated : ''} ${className}`, style: {
46
+ width,
47
+ height,
48
+ borderRadius,
49
+ } }));
50
+ }
51
+ return (React.createElement("div", { className: className }, Array.from({ length: lines }, (_, index) => (React.createElement("div", { key: index, className: `${SkeletonLoader_module_css_1.default.skeleton} ${animated ? SkeletonLoader_module_css_1.default.animated : ''} ${index < lines - 1 ? SkeletonLoader_module_css_1.default.marginBottom : ''}`, style: {
52
+ width: index === lines - 1 ? '60%' : '100%',
53
+ height,
54
+ borderRadius,
55
+ } })))));
56
+ }
57
+ // Table row skeleton component
58
+ function TableRowSkeleton({ columns = 5 }) {
59
+ return (React.createElement("tr", { className: SkeletonLoader_module_css_1.default.tableRow },
60
+ React.createElement("td", { className: SkeletonLoader_module_css_1.default.tableCell, style: { width: '48px' } },
61
+ React.createElement(SkeletonLoader, { width: 18, height: 18, borderRadius: 6 })),
62
+ Array.from({ length: columns }, (_, index) => (React.createElement("td", { key: index, className: SkeletonLoader_module_css_1.default.tableCell },
63
+ React.createElement(SkeletonLoader, { width: "18vw", height: 18 })))),
64
+ React.createElement("td", { className: SkeletonLoader_module_css_1.default.tableCell },
65
+ React.createElement("div", { className: SkeletonLoader_module_css_1.default.actionButtons },
66
+ React.createElement(SkeletonLoader, { width: 22, height: 18, borderRadius: 6 }),
67
+ React.createElement(SkeletonLoader, { width: 22, height: 18, borderRadius: 6 }),
68
+ React.createElement(SkeletonLoader, { width: 22, height: 18, borderRadius: 6 })))));
69
+ }
70
+ exports.default = SkeletonLoader;
71
+ //# sourceMappingURL=SkeletonLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkeletonLoader.js","sourceRoot":"","sources":["../../../src/components/SkeletonLoader.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,wCAoCC;AAGD,4CAoBC;AAvED,6CAA+B;AAC/B,4FAAgD;AAWhD,SAAgB,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,mCAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,mCAAK,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,mCAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,mCAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,mCAAK,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,SAAgB,gBAAgB,CAAC,EAAE,OAAO,GAAG,CAAC,EAAwB;IACpE,OAAO,CACL,4BAAI,SAAS,EAAE,mCAAK,CAAC,QAAQ;QAC3B,4BAAI,SAAS,EAAE,mCAAK,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,mCAAK,CAAC,SAAS;YACxC,oBAAC,cAAc,IAAC,KAAK,EAAC,MAAM,EAAC,MAAM,EAAE,EAAE,GAAI,CACxC,CACN,CAAC;QACF,4BAAI,SAAS,EAAE,mCAAK,CAAC,SAAS;YAC5B,6BAAK,SAAS,EAAE,mCAAK,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,kBAAe,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 declare function Spinner({ active }: {
1
+ import * as React from 'react';
2
+ export interface SpinnerProps {
3
+ /** Render nothing when false. */
3
4
  active?: boolean;
4
- }): React.JSX.Element;
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,12 +1,64 @@
1
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
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.Spinner = Spinner;
7
- const react_1 = __importDefault(require("react"));
40
+ const React = __importStar(require("react"));
8
41
  const Spinner_module_css_1 = __importDefault(require("./Spinner.module.css"));
9
- function Spinner({ active = true }) {
10
- return (react_1.default.createElement("div", { className: [Spinner_module_css_1.default.spinner, active && Spinner_module_css_1.default.active].join(' '), role: "progressbar", "aria-busy": active ? 'true' : 'false' }));
42
+ const ClassNames_1 = require("@_linked/react/utils/ClassNames");
43
+ const useReducedMotion_js_1 = require("../hooks/useReducedMotion.js");
44
+ /**
45
+ * A loading indicator: a ring that inherits its colour, so it reads on any background.
46
+ *
47
+ * Two things it does that a bare CSS spin does not. It honours
48
+ * `prefers-reduced-motion` — a spinner is the most common thing to make someone with
49
+ * vestibular sensitivity look away — showing a still ring rather than nothing, so the
50
+ * "busy" signal survives. And it renders **nothing** when inactive, so it can be dropped
51
+ * into a scroll sentinel or a button without reserving space it is not using.
52
+ *
53
+ * The ring is an inner element rather than a border on the container. A border on the
54
+ * container is distorted the moment a flex parent stretches it; an explicitly sized inner
55
+ * element stays circular.
56
+ */
57
+ function Spinner({ active = true, size = 'small', className, label = 'Loading', }) {
58
+ const reducedMotion = (0, useReducedMotion_js_1.useReducedMotion)();
59
+ if (!active)
60
+ return null;
61
+ return (React.createElement("div", { className: (0, ClassNames_1.cl)(Spinner_module_css_1.default.spinner, Spinner_module_css_1.default[size], className), role: "progressbar", "aria-busy": "true", "aria-label": label },
62
+ React.createElement("span", { className: (0, ClassNames_1.cl)(Spinner_module_css_1.default.ring, reducedMotion && Spinner_module_css_1.default.reducedMotion), "aria-hidden": "true" })));
11
63
  }
12
64
  //# sourceMappingURL=Spinner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Spinner.js","sourceRoot":"","sources":["../../../src/components/Spinner.tsx"],"names":[],"mappings":";;;;;AAGA,0BAQC;AAXD,kDAA0B;AAC1B,8EAAyC;AAEzC,SAAgB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE;IACvC,OAAO,CACL,uCACE,SAAS,EAAE,CAAC,4BAAK,CAAC,OAAO,EAAE,MAAM,IAAI,4BAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAC5D,IAAI,EAAC,aAAa,eACP,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,GACpC,CACH,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Spinner.js","sourceRoot":"","sources":["../../../src/components/Spinner.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,0BAuBC;AAlDD,6CAA+B;AAC/B,8EAAyC;AACzC,gEAAmD;AACnD,sEAA8D;AAW9D;;;;;;;;;;;;GAYG;AACH,SAAgB,OAAO,CAAC,EACtB,MAAM,GAAG,IAAI,EACb,IAAI,GAAG,OAAO,EACd,SAAS,EACT,KAAK,GAAG,SAAS,GACJ;IACb,MAAM,aAAa,GAAG,IAAA,sCAAgB,GAAE,CAAC;IAEzC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,CACL,6BACE,SAAS,EAAE,IAAA,eAAE,EAAC,4BAAK,CAAC,OAAO,EAAE,4BAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACpD,IAAI,EAAC,aAAa,eACR,MAAM,gBACJ,KAAK;QAEjB,8BACE,SAAS,EAAE,IAAA,eAAE,EAAC,4BAAK,CAAC,IAAI,EAAE,aAAa,IAAI,4BAAK,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
- border: var(--spinner-border-width, 4px) solid
6
- var(--spinner-track-color, transparent);
7
- border-top-color: var(--spinner-indicator-color, var(--meter-indicator-bg));
8
- border-radius: 50%;
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
- animation: spin var(--spinner-animation-duration, 1s) linear infinite;
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 spin {
15
- 0% {
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,130 @@
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.TruncatedText = TruncatedText;
49
+ const Dialog_js_1 = require("./Dialog.js");
50
+ const HoverCard_js_1 = require("./HoverCard.js");
51
+ const IconButton_js_1 = require("./IconButton.js");
52
+ const react_1 = __importStar(require("react"));
53
+ const TruncatedText_module_css_1 = __importDefault(require("./TruncatedText.module.css"));
54
+ /**
55
+ * Two inline glyphs rather than a dependency on an icon set.
56
+ *
57
+ * This component needs exactly two, and a library whose contract is "a behaviour source plus
58
+ * a stylesheet" should not make every consumer install an icon package to draw a tick. They
59
+ * inherit `currentColor`, so they follow the button they sit in.
60
+ */
61
+ const CheckGlyph = () => (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
62
+ react_1.default.createElement("polyline", { points: "20 6 9 17 4 12" })));
63
+ const CopyGlyph = () => (react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
64
+ react_1.default.createElement("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
65
+ react_1.default.createElement("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })));
66
+ /**
67
+ * TruncatedText - Displays truncated text with hover/tap to show full content
68
+ *
69
+ * For long text fields, shows first N characters with ellipsis.
70
+ * On desktop: hover displays full text in a popover with copy button.
71
+ * On mobile: tap opens a dialog with full text and copy button.
72
+ */
73
+ function TruncatedText({ text, maxLength = 80 }) {
74
+ const [copied, setCopied] = (0, react_1.useState)(false);
75
+ const [isMobile, setIsMobile] = (0, react_1.useState)(false);
76
+ (0, react_1.useEffect)(() => {
77
+ // Check for touch device or small screen
78
+ const checkMobile = () => {
79
+ const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
80
+ const isSmallScreen = window.matchMedia('(max-width: 768px)').matches;
81
+ setIsMobile(hasTouchScreen || isSmallScreen);
82
+ };
83
+ checkMobile();
84
+ window.addEventListener('resize', checkMobile);
85
+ return () => window.removeEventListener('resize', checkMobile);
86
+ }, []);
87
+ // If text is short enough, just render it directly
88
+ if (!text || text.length <= maxLength) {
89
+ return react_1.default.createElement("span", null, text || '-');
90
+ }
91
+ const truncatedText = text.slice(0, maxLength) + '...';
92
+ const handleCopy = (e) => __awaiter(this, void 0, void 0, function* () {
93
+ e.stopPropagation();
94
+ try {
95
+ yield navigator.clipboard.writeText(text);
96
+ setCopied(true);
97
+ setTimeout(() => setCopied(false), 2000);
98
+ }
99
+ catch (err) {
100
+ console.error('Failed to copy text:', err);
101
+ }
102
+ });
103
+ // Mobile: Use Dialog triggered by tap
104
+ if (isMobile) {
105
+ return (react_1.default.createElement(Dialog_js_1.Dialog.Root, null,
106
+ react_1.default.createElement(Dialog_js_1.Dialog.Trigger, { asChild: true },
107
+ react_1.default.createElement("span", { className: TruncatedText_module_css_1.default.truncatedText }, truncatedText)),
108
+ react_1.default.createElement(Dialog_js_1.Dialog.Content, { className: TruncatedText_module_css_1.default.dialogContent },
109
+ react_1.default.createElement(Dialog_js_1.Dialog.Header, null,
110
+ react_1.default.createElement(Dialog_js_1.Dialog.Title, null, "Full Text")),
111
+ react_1.default.createElement("div", { className: TruncatedText_module_css_1.default.dialogBody },
112
+ react_1.default.createElement("div", { className: TruncatedText_module_css_1.default.scrollArea },
113
+ react_1.default.createElement("p", { className: TruncatedText_module_css_1.default.fullText }, text))),
114
+ react_1.default.createElement("div", { className: TruncatedText_module_css_1.default.dialogFooter },
115
+ react_1.default.createElement(IconButton_js_1.IconButton, { size: "small", variant: "ghost", className: TruncatedText_module_css_1.default.copyButton, onClick: handleCopy, "aria-label": copied ? 'Copied!' : 'Copy to clipboard' }, copied ? (react_1.default.createElement(CheckGlyph, null)) : (react_1.default.createElement(CopyGlyph, null)))))));
116
+ }
117
+ // Desktop: Use HoverCard triggered by hover
118
+ return (react_1.default.createElement(HoverCard_js_1.HoverCard.Root, { openDelay: 300, closeDelay: 200 },
119
+ react_1.default.createElement(HoverCard_js_1.HoverCard.Trigger, { asChild: true },
120
+ react_1.default.createElement("span", { className: TruncatedText_module_css_1.default.truncatedText }, truncatedText)),
121
+ react_1.default.createElement(HoverCard_js_1.HoverCard.Portal, null,
122
+ react_1.default.createElement(HoverCard_js_1.HoverCard.Content, { side: "top", align: "start", sideOffset: 8, className: TruncatedText_module_css_1.default.hoverContent },
123
+ react_1.default.createElement("div", { className: TruncatedText_module_css_1.default.contentWrapper },
124
+ react_1.default.createElement("div", { className: TruncatedText_module_css_1.default.scrollArea },
125
+ react_1.default.createElement("p", { className: TruncatedText_module_css_1.default.fullText }, text)),
126
+ react_1.default.createElement("div", { className: TruncatedText_module_css_1.default.copyButtonWrapper },
127
+ react_1.default.createElement(IconButton_js_1.IconButton, { size: "small", variant: "ghost", className: TruncatedText_module_css_1.default.copyButton, onClick: handleCopy, "aria-label": copied ? 'Copied!' : 'Copy to clipboard' }, copied ? (react_1.default.createElement(CheckGlyph, null)) : (react_1.default.createElement(CopyGlyph, null))))),
128
+ react_1.default.createElement(HoverCard_js_1.HoverCard.Arrow, { className: TruncatedText_module_css_1.default.arrow })))));
129
+ }
130
+ //# 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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"}
@@ -0,0 +1,162 @@
1
+ @import './helpers/mixins.css';
2
+ @import '@_linked/css/package.css';
3
+
4
+ /* ============================================================================
5
+ TruncatedText.module.css
6
+
7
+ Truncated text component with hover popover for full content.
8
+ Uses semantic tokens from theme.css.
9
+ ============================================================================ */
10
+
11
+ /* === Truncated Text Trigger === */
12
+ .truncatedText {
13
+ cursor: help;
14
+ border-bottom: 1px dotted var(--truncated-text-gray-400, var(--color-gray-400));
15
+ }
16
+
17
+ /* === Hover Card Content === */
18
+ .hoverContent {
19
+ background-color: var(--surface-bg);
20
+ border: 1px solid var(--surface-border);
21
+ border-radius: var(--radius-field);
22
+ box-shadow: var(--surface-shadow);
23
+ padding: var(--space-sm);
24
+ max-width: 400px;
25
+ min-width: 200px;
26
+ z-index: 1000;
27
+ }
28
+
29
+ /* === Content Wrapper === */
30
+ .contentWrapper {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--space-xs);
34
+ }
35
+
36
+ /* === Scroll Area === */
37
+ .scrollArea {
38
+ max-height: 150px;
39
+ overflow-y: auto;
40
+ }
41
+
42
+ /* === Full Text === */
43
+ .fullText {
44
+ font-size: var(--typography-small-size);
45
+ color: var(--text-primary);
46
+ line-height: 1.5;
47
+ margin: 0;
48
+ white-space: pre-wrap;
49
+ word-break: break-word;
50
+ }
51
+
52
+ /* === Copy Button Wrapper === */
53
+ .copyButtonWrapper {
54
+ display: flex;
55
+ justify-content: flex-end;
56
+ border-top: 1px solid var(--surface-border);
57
+ padding-top: var(--space-xs);
58
+ margin-top: var(--space-xs);
59
+ }
60
+
61
+ /* === Copy Button === */
62
+ .copyButton {
63
+ color: var(--truncated-text-tertiary-500, var(--color-tertiary-500)) !important;
64
+ padding: var(--space-xs) !important;
65
+ }
66
+
67
+ .copyButton:hover {
68
+ color: var(--truncated-text-tertiary-600, var(--color-tertiary-600)) !important;
69
+ background-color: color-mix(
70
+ in srgb,
71
+ var(--truncated-text-tertiary-500, var(--color-tertiary-500)) 12%,
72
+ transparent
73
+ ) !important;
74
+ }
75
+
76
+ /* === Arrow === */
77
+ .arrow {
78
+ fill: var(--surface-bg);
79
+ }
80
+
81
+ /* ============================================================================
82
+ Mobile Dialog Styles
83
+ ============================================================================ */
84
+
85
+ .dialogContent {
86
+ max-width: 90vw;
87
+ width: 400px;
88
+ background-color: var(--surface-bg);
89
+ border: 1px solid var(--surface-border);
90
+ }
91
+
92
+ .dialogBody {
93
+ padding: var(--space-sm) 0;
94
+ color: var(--text-primary);
95
+ }
96
+
97
+ .dialogFooter {
98
+ display: flex;
99
+ justify-content: flex-end;
100
+ align-items: center;
101
+ padding-top: var(--space-xs);
102
+ border-top: 1px solid var(--surface-border);
103
+ }
104
+
105
+ /* ============================================================================
106
+ Dark Mode
107
+ ============================================================================ */
108
+
109
+ [data-theme='dark'] .truncatedText {
110
+ border-bottom-color: var(--truncated-text-gray-500, var(--color-gray-500));
111
+ }
112
+
113
+ [data-theme='dark'] .hoverContent {
114
+ background-color: var(--surface-bg);
115
+ border-color: var(--surface-border);
116
+ }
117
+
118
+ /* .fullText uses --text-primary which auto-adjusts for dark mode */
119
+
120
+ [data-theme='dark'] .copyButtonWrapper {
121
+ border-top-color: var(--surface-border);
122
+ }
123
+
124
+ [data-theme='dark'] .copyButton {
125
+ color: var(--truncated-text-tertiary-400, var(--color-tertiary-400)) !important;
126
+ }
127
+
128
+ [data-theme='dark'] .copyButton:hover {
129
+ color: var(--truncated-text-tertiary-300, var(--color-tertiary-300)) !important;
130
+ background-color: color-mix(
131
+ in srgb,
132
+ var(--truncated-text-tertiary-500, var(--color-tertiary-500)) 15%,
133
+ transparent
134
+ ) !important;
135
+ }
136
+
137
+ [data-theme='dark'] .arrow {
138
+ fill: var(--surface-bg);
139
+ }
140
+
141
+ [data-theme='dark'] .dialogContent {
142
+ background-color: var(--surface-bg);
143
+ border-color: var(--surface-border);
144
+ }
145
+
146
+ /* .dialogBody uses --text-primary which auto-adjusts for dark mode */
147
+
148
+ [data-theme='dark'] .dialogFooter {
149
+ border-top-color: var(--surface-border);
150
+ }
151
+
152
+ /* Dialog close button in dark mode */
153
+ [data-theme='dark'] .dialogContent button[aria-label="Close"],
154
+ [data-theme='dark'] .dialogContent [class*="Close"] {
155
+ color: var(--truncated-text-gray-400, var(--color-gray-400));
156
+ }
157
+
158
+ [data-theme='dark'] .dialogContent button[aria-label="Close"]:hover,
159
+ [data-theme='dark'] .dialogContent [class*="Close"]:hover {
160
+ color: var(--truncated-text-gray-200, var(--color-gray-200));
161
+ background-color: var(--truncated-text-gray-700, var(--color-gray-700));
162
+ }