@stll/workspace-ui 0.12.6 → 0.13.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.
@@ -0,0 +1,9 @@
1
+ import { PreviewMatter, PreviewPerson } from "./mock-data.js";
2
+ import { FeaturePreviewProps } from "./surface.js";
3
+ import { BillingPreview, BillingPreviewData, BillingPreviewEntry, BillingPreviewLabels } from "./billing-preview.js";
4
+ import { ContactsPreview, ContactsPreviewData, ContactsPreviewLabels } from "./contacts-preview.js";
5
+ import { DocumentsPreview, DocumentsPreviewComment, DocumentsPreviewData, DocumentsPreviewLabels } from "./documents-preview.js";
6
+ import { MattersPreview, MattersPreviewData, MattersPreviewLabels, MattersPreviewTask, MattersPreviewTaskStatus } from "./matters-preview.js";
7
+ import { ResearchPreview, ResearchPreviewData, ResearchPreviewDecision, ResearchPreviewLabels } from "./research-preview.js";
8
+ import { TimeTrackingPreview, TimeTrackingPreviewData, TimeTrackingPreviewEntry, TimeTrackingPreviewLabels } from "./time-tracking-preview.js";
9
+ export { BillingPreview, type BillingPreviewData, type BillingPreviewEntry, type BillingPreviewLabels, ContactsPreview, type ContactsPreviewData, type ContactsPreviewLabels, DocumentsPreview, type DocumentsPreviewComment, type DocumentsPreviewData, type DocumentsPreviewLabels, type FeaturePreviewProps, MattersPreview, type MattersPreviewData, type MattersPreviewLabels, type MattersPreviewTask, type MattersPreviewTaskStatus, type PreviewMatter, type PreviewPerson, ResearchPreview, type ResearchPreviewData, type ResearchPreviewDecision, type ResearchPreviewLabels, TimeTrackingPreview, type TimeTrackingPreviewData, type TimeTrackingPreviewEntry, type TimeTrackingPreviewLabels };
@@ -0,0 +1,7 @@
1
+ import { BillingPreview } from "./billing-preview.js";
2
+ import { ContactsPreview } from "./contacts-preview.js";
3
+ import { DocumentsPreview } from "./documents-preview.js";
4
+ import { MattersPreview } from "./matters-preview.js";
5
+ import { ResearchPreview } from "./research-preview.js";
6
+ import { TimeTrackingPreview } from "./time-tracking-preview.js";
7
+ export { BillingPreview, ContactsPreview, DocumentsPreview, MattersPreview, ResearchPreview, TimeTrackingPreview };
@@ -0,0 +1,30 @@
1
+ import { PreviewMatter, PreviewPerson } from "./mock-data.js";
2
+ import { FeaturePreviewProps } from "./surface.js";
3
+ //#region src/feature-previews/matters-preview.d.ts
4
+ export type MattersPreviewLabels = {
5
+ /** Column names: the task statuses the board groups by. */
6
+ open: string;
7
+ inProgress: string;
8
+ done: string;
9
+ };
10
+ export type MattersPreviewTaskStatus = "open" | "in_progress" | "done";
11
+ export type MattersPreviewTask = {
12
+ name: string;
13
+ status: MattersPreviewTaskStatus;
14
+ /** `YYYY-MM-DD`. */
15
+ due: string;
16
+ assignee: PreviewPerson;
17
+ /** File name of an attached document, if any. */
18
+ attachment: string | null;
19
+ };
20
+ /** The board's content: the matter in the title row, its team, and the tasks
21
+ * (grouped into columns by status, in the given order). */
22
+ export type MattersPreviewData = {
23
+ matter: PreviewMatter;
24
+ team: readonly PreviewPerson[];
25
+ tasks: readonly MattersPreviewTask[];
26
+ };
27
+ /** A matter's task board: three status columns of task cards with due dates,
28
+ * assignees and an attached document. */
29
+ export declare const MattersPreview: ({ labels, locale, data, className }: FeaturePreviewProps<MattersPreviewLabels, MattersPreviewData>) => import("react").JSX.Element;
30
+ //#endregion
@@ -0,0 +1,153 @@
1
+ import { TASK_STATUS_COLORS, TASK_STATUS_ICONS } from "../task-status.js";
2
+ import { formatPreviewCount, formatPreviewDate } from "./format.js";
3
+ import { PREVIEW_MATTERS, PREVIEW_PEOPLE } from "./mock-data.js";
4
+ import { FeaturePreviewSurface, PreviewAvatar, PreviewAvatarStack } from "./surface.js";
5
+ import { CalendarIcon, FileTextIcon } from "@stll/ui/icons";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { cn } from "@stll/ui/utils";
8
+ import { KanbanCardShell, KanbanColumnHeader } from "@stll/ui/kanban";
9
+ import { MatterIcon } from "@stll/ui/matter-icon";
10
+ //#region src/feature-previews/matters-preview.tsx
11
+ /** A matter's task board: three status columns of task cards with due dates,
12
+ * assignees and an attached document. */
13
+ const MattersPreview = ({ labels, locale, data = DEFAULT_DATA, className }) => /* @__PURE__ */ jsx(FeaturePreviewSurface, {
14
+ className,
15
+ icon: /* @__PURE__ */ jsx(MatterIcon, {
16
+ className: "size-4 shrink-0",
17
+ matter: data.matter
18
+ }),
19
+ meta: /* @__PURE__ */ jsx(PreviewAvatarStack, { people: data.team }),
20
+ title: data.matter.name,
21
+ children: /* @__PURE__ */ jsx("div", {
22
+ className: "grid h-full grid-cols-3 gap-2 p-2",
23
+ children: COLUMNS.map((column) => {
24
+ const tasks = data.tasks.filter((task) => task.status === column.status);
25
+ const Icon = TASK_STATUS_ICONS[column.status];
26
+ return /* @__PURE__ */ jsxs("div", {
27
+ className: "bg-muted/50 flex min-w-0 flex-col gap-1.5 rounded-md pb-1.5",
28
+ children: [/* @__PURE__ */ jsx(KanbanColumnHeader, {
29
+ className: "h-8 px-2",
30
+ meta: formatPreviewCount(locale, tasks.length),
31
+ swatch: /* @__PURE__ */ jsx(Icon, { className: cn("size-3.5 shrink-0", TASK_STATUS_COLORS[column.status]) }),
32
+ title: labels[column.label]
33
+ }), /* @__PURE__ */ jsx("div", {
34
+ className: "flex min-w-0 flex-col gap-1.5 px-1.5",
35
+ children: tasks.map((task) => /* @__PURE__ */ jsx(PreviewTaskCard, {
36
+ locale,
37
+ task
38
+ }, task.name))
39
+ })]
40
+ }, column.status);
41
+ })
42
+ })
43
+ });
44
+ const PreviewTaskCard = ({ task, locale }) => {
45
+ const Icon = TASK_STATUS_ICONS[task.status];
46
+ return /* @__PURE__ */ jsx(KanbanCardShell, {
47
+ className: "p-2.5",
48
+ children: /* @__PURE__ */ jsxs("div", {
49
+ className: "flex flex-col gap-2",
50
+ children: [
51
+ /* @__PURE__ */ jsxs("span", {
52
+ className: "flex min-w-0 items-start gap-1.5 leading-snug font-medium",
53
+ children: [/* @__PURE__ */ jsx(Icon, { className: cn("mt-px size-3.5 shrink-0", TASK_STATUS_COLORS[task.status]) }), /* @__PURE__ */ jsx("span", {
54
+ className: "line-clamp-2 min-w-0",
55
+ children: task.name
56
+ })]
57
+ }),
58
+ task.attachment === null ? null : /* @__PURE__ */ jsxs("span", {
59
+ className: "text-muted-foreground flex min-w-0 items-center gap-1.5",
60
+ children: [/* @__PURE__ */ jsx(FileTextIcon, { className: "size-3 shrink-0" }), /* @__PURE__ */ jsx("span", {
61
+ className: "truncate",
62
+ children: task.attachment
63
+ })]
64
+ }),
65
+ /* @__PURE__ */ jsxs("div", {
66
+ className: "text-muted-foreground flex min-w-0 items-center gap-1.5 leading-none",
67
+ children: [/* @__PURE__ */ jsxs("span", {
68
+ className: "bg-muted/60 flex min-w-0 items-center gap-1 rounded px-1.5 py-0.5 whitespace-nowrap tabular-nums",
69
+ children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "size-3 shrink-0" }), formatPreviewDate({
70
+ locale,
71
+ date: task.due,
72
+ options: {
73
+ day: "numeric",
74
+ month: "short"
75
+ }
76
+ })]
77
+ }), /* @__PURE__ */ jsx("span", {
78
+ className: "ms-auto",
79
+ children: /* @__PURE__ */ jsx(PreviewAvatar, { person: task.assignee })
80
+ })]
81
+ })
82
+ ]
83
+ })
84
+ });
85
+ };
86
+ const COLUMNS = [
87
+ {
88
+ status: "open",
89
+ label: "open"
90
+ },
91
+ {
92
+ status: "in_progress",
93
+ label: "inProgress"
94
+ },
95
+ {
96
+ status: "done",
97
+ label: "done"
98
+ }
99
+ ];
100
+ const DEFAULT_DATA = {
101
+ matter: PREVIEW_MATTERS.cargo,
102
+ team: [
103
+ PREVIEW_PEOPLE.laura,
104
+ PREVIEW_PEOPLE.tomas,
105
+ PREVIEW_PEOPLE.amira
106
+ ],
107
+ tasks: [
108
+ {
109
+ name: "Draft statement of claim",
110
+ status: "open",
111
+ due: "2026-10-28",
112
+ assignee: PREVIEW_PEOPLE.laura,
113
+ attachment: null
114
+ },
115
+ {
116
+ name: "Collect shipping manifests",
117
+ status: "open",
118
+ due: "2026-10-30",
119
+ assignee: PREVIEW_PEOPLE.tomas,
120
+ attachment: null
121
+ },
122
+ {
123
+ name: "Review expert report on cargo damage",
124
+ status: "in_progress",
125
+ due: "2026-10-21",
126
+ assignee: PREVIEW_PEOPLE.amira,
127
+ attachment: "Expert report – cargo damage.pdf"
128
+ },
129
+ {
130
+ name: "Prepare witness statements",
131
+ status: "in_progress",
132
+ due: "2026-10-23",
133
+ assignee: PREVIEW_PEOPLE.laura,
134
+ attachment: null
135
+ },
136
+ {
137
+ name: "Send letter before action",
138
+ status: "done",
139
+ due: "2026-10-12",
140
+ assignee: PREVIEW_PEOPLE.tomas,
141
+ attachment: "Letter before action.docx"
142
+ },
143
+ {
144
+ name: "Run conflict check",
145
+ status: "done",
146
+ due: "2026-10-02",
147
+ assignee: PREVIEW_PEOPLE.amira,
148
+ attachment: null
149
+ }
150
+ ]
151
+ };
152
+ //#endregion
153
+ export { MattersPreview };
@@ -0,0 +1,41 @@
1
+ //#region src/feature-previews/mock-data.d.ts
2
+ /** A person drawn as initials (assignees, reviewers, comment authors). */
3
+ export type PreviewPerson = {
4
+ name: string;
5
+ };
6
+ export declare const PREVIEW_PEOPLE: {
7
+ readonly laura: {
8
+ readonly name: "Laura Chen";
9
+ };
10
+ readonly tomas: {
11
+ readonly name: "Tomás Novák";
12
+ };
13
+ readonly amira: {
14
+ readonly name: "Amira Haddad";
15
+ };
16
+ };
17
+ /** A matter drawn with its icon. `color` is a matter colour token
18
+ * (`--option-blue`, `--option-violet`, …); `id` seeds the fallback colour. */
19
+ export type PreviewMatter = {
20
+ id: string;
21
+ color: string;
22
+ name: string;
23
+ };
24
+ export declare const PREVIEW_MATTERS: {
25
+ readonly cargo: {
26
+ readonly id: "preview-matter-cargo";
27
+ readonly color: "--option-blue";
28
+ readonly name: "Harbour Logistics v. Meridian Freight";
29
+ };
30
+ readonly acquisition: {
31
+ readonly id: "preview-matter-acquisition";
32
+ readonly color: "--option-violet";
33
+ readonly name: "Project Alder: share purchase";
34
+ };
35
+ readonly lease: {
36
+ readonly id: "preview-matter-lease";
37
+ readonly color: "--option-amber";
38
+ readonly name: "Quayside warehouse lease";
39
+ };
40
+ };
41
+ //#endregion
@@ -0,0 +1,25 @@
1
+ //#region src/feature-previews/mock-data.ts
2
+ const PREVIEW_PEOPLE = {
3
+ laura: { name: "Laura Chen" },
4
+ tomas: { name: "Tomás Novák" },
5
+ amira: { name: "Amira Haddad" }
6
+ };
7
+ const PREVIEW_MATTERS = {
8
+ cargo: {
9
+ id: "preview-matter-cargo",
10
+ color: "--option-blue",
11
+ name: "Harbour Logistics v. Meridian Freight"
12
+ },
13
+ acquisition: {
14
+ id: "preview-matter-acquisition",
15
+ color: "--option-violet",
16
+ name: "Project Alder: share purchase"
17
+ },
18
+ lease: {
19
+ id: "preview-matter-lease",
20
+ color: "--option-amber",
21
+ name: "Quayside warehouse lease"
22
+ }
23
+ };
24
+ //#endregion
25
+ export { PREVIEW_MATTERS, PREVIEW_PEOPLE };
@@ -0,0 +1,33 @@
1
+ import { FeaturePreviewProps } from "./surface.js";
2
+ import { COURT_TIER_WEIGHT } from "@stll/ui/document-identity-badge.logic";
3
+ //#region src/feature-previews/research-preview.d.ts
4
+ export type ResearchPreviewLabels = {
5
+ /** Title of the case-law search surface. */
6
+ caseLaw: string;
7
+ };
8
+ export type ResearchPreviewDecision = {
9
+ court: {
10
+ name: string;
11
+ /** Badge text (`NS`, `ÚS`). */
12
+ abbreviation: string;
13
+ /** Badge weight, from solid (constitutional) to dashed (other). */
14
+ tier: keyof typeof COURT_TIER_WEIGHT;
15
+ };
16
+ /** `YYYY-MM-DD`. */
17
+ date: string;
18
+ /** Unique within the results. */
19
+ fileNumber: string;
20
+ /** The matched passage: `hit` is highlighted between `before` and
21
+ * `after`. */
22
+ before: string;
23
+ hit: string;
24
+ after: string;
25
+ };
26
+ export type ResearchPreviewData = {
27
+ query: string;
28
+ decisions: readonly ResearchPreviewDecision[];
29
+ };
30
+ /** Case-law search: a query and the decisions it found, each with its court,
31
+ * date, file number and the passage that matched. */
32
+ export declare const ResearchPreview: ({ labels, locale, data, className }: FeaturePreviewProps<ResearchPreviewLabels, ResearchPreviewData>) => import("react").JSX.Element;
33
+ //#endregion
@@ -0,0 +1,115 @@
1
+ import { formatPreviewDate } from "./format.js";
2
+ import { FeaturePreviewSurface } from "./surface.js";
3
+ import { CaseLawIcon, SearchIcon } from "@stll/ui/icons";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { SEARCH_HIT_MARK, TextMark } from "@stll/ui/text-mark";
6
+ import { DocumentIdentityBadge } from "@stll/ui/document-identity-badge";
7
+ //#region src/feature-previews/research-preview.tsx
8
+ /** Case-law search: a query and the decisions it found, each with its court,
9
+ * date, file number and the passage that matched. */
10
+ const ResearchPreview = ({ labels, locale, data = DEFAULT_DATA, className }) => /* @__PURE__ */ jsx(FeaturePreviewSurface, {
11
+ className,
12
+ icon: /* @__PURE__ */ jsx(CaseLawIcon, { className: "text-muted-foreground size-4 shrink-0" }),
13
+ title: labels.caseLaw,
14
+ children: /* @__PURE__ */ jsxs("div", {
15
+ className: "flex h-full min-h-0 flex-col gap-2 p-3",
16
+ children: [/* @__PURE__ */ jsxs("div", {
17
+ className: "bg-card flex h-9 shrink-0 items-center gap-2 rounded-md border px-2.5 text-sm shadow-xs",
18
+ children: [/* @__PURE__ */ jsx(SearchIcon, { className: "text-muted-foreground size-4 shrink-0" }), /* @__PURE__ */ jsx("span", {
19
+ className: "truncate",
20
+ children: data.query
21
+ })]
22
+ }), /* @__PURE__ */ jsx("ol", {
23
+ className: "flex min-h-0 flex-col divide-y",
24
+ children: data.decisions.map((decision) => /* @__PURE__ */ jsxs("li", {
25
+ className: "flex flex-col gap-1 py-2.5",
26
+ children: [
27
+ /* @__PURE__ */ jsxs("div", {
28
+ className: "flex min-w-0 items-center gap-2",
29
+ children: [
30
+ /* @__PURE__ */ jsx(DocumentIdentityBadge, { identity: {
31
+ kind: "decision",
32
+ courtAbbreviation: decision.court.abbreviation,
33
+ courtTier: decision.court.tier
34
+ } }),
35
+ /* @__PURE__ */ jsx("span", {
36
+ className: "truncate text-sm font-medium",
37
+ children: decision.court.name
38
+ }),
39
+ /* @__PURE__ */ jsx("span", {
40
+ className: "text-muted-foreground ms-auto shrink-0 whitespace-nowrap tabular-nums",
41
+ children: formatPreviewDate({
42
+ locale,
43
+ date: decision.date,
44
+ options: {
45
+ day: "numeric",
46
+ month: "short",
47
+ year: "numeric"
48
+ }
49
+ })
50
+ })
51
+ ]
52
+ }),
53
+ /* @__PURE__ */ jsx("bdi", {
54
+ className: "text-muted-foreground text-2xs font-mono",
55
+ children: decision.fileNumber
56
+ }),
57
+ /* @__PURE__ */ jsxs("p", {
58
+ className: "text-foreground-strong-muted line-clamp-2 leading-relaxed",
59
+ children: [
60
+ decision.before,
61
+ /* @__PURE__ */ jsx(TextMark, {
62
+ ...SEARCH_HIT_MARK,
63
+ children: decision.hit
64
+ }),
65
+ decision.after
66
+ ]
67
+ })
68
+ ]
69
+ }, decision.fileNumber))
70
+ })]
71
+ })
72
+ });
73
+ const DEFAULT_DATA = {
74
+ query: "limitation period for warranty claims under a share purchase",
75
+ decisions: [
76
+ {
77
+ court: {
78
+ name: "Supreme Court",
79
+ abbreviation: "NS",
80
+ tier: "supreme"
81
+ },
82
+ date: "2025-03-18",
83
+ fileNumber: "27 Cdo 1931/2024",
84
+ before: "For a claim under a share purchase agreement, the ",
85
+ hit: "limitation period",
86
+ after: " begins when the buyer learned of the breach of warranty, not on the day of completion."
87
+ },
88
+ {
89
+ court: {
90
+ name: "Constitutional Court",
91
+ abbreviation: "ÚS",
92
+ tier: "constitutional"
93
+ },
94
+ date: "2024-06-04",
95
+ fileNumber: "II. ÚS 1187/23",
96
+ before: "A court that applies the ",
97
+ hit: "limitation period",
98
+ after: " without asking when the claimant could know of the defect denies a fair hearing."
99
+ },
100
+ {
101
+ court: {
102
+ name: "High Court in Prague",
103
+ abbreviation: "VS",
104
+ tier: "regional"
105
+ },
106
+ date: "2023-11-29",
107
+ fileNumber: "5 Cmo 112/2023",
108
+ before: "Contractual caps on ",
109
+ hit: "warranty claims",
110
+ after: " do not shorten the statutory limitation period unless the parties say so expressly."
111
+ }
112
+ ]
113
+ };
114
+ //#endregion
115
+ export { ResearchPreview };
@@ -0,0 +1,34 @@
1
+ import { PreviewPerson } from "./mock-data.js";
2
+ import * as React from "react";
3
+ //#region src/feature-previews/surface.d.ts
4
+ /** Props every scene takes: its interface labels, the formatting locale for
5
+ * its dates and numbers, its content, and classes for the host's sizing. */
6
+ export type FeaturePreviewProps<Labels, Data> = {
7
+ labels: Labels;
8
+ /** Full formatting locale (`en-GB`, `cs-CZ`), used for dates and numbers. */
9
+ locale: string;
10
+ /** What the scene depicts (matters, documents, amounts). Omitted, the
11
+ * scene shows the package's fictional English set. */
12
+ data?: Data | undefined;
13
+ className?: string | undefined;
14
+ };
15
+ type FeaturePreviewSurfaceProps = React.PropsWithChildren<{
16
+ icon: React.ReactNode;
17
+ title: React.ReactNode;
18
+ meta?: React.ReactNode;
19
+ className?: string | undefined;
20
+ }>;
21
+ /**
22
+ * The window every scene draws in: a title row and a clipped body that fills
23
+ * whatever box the host gives it. Decorative, so it is hidden from assistive
24
+ * technology and takes no pointer input; the host names what it illustrates.
25
+ */
26
+ export declare const FeaturePreviewSurface: ({ icon, title, meta, className, children }: FeaturePreviewSurfaceProps) => React.JSX.Element;
27
+ /** A row of overlapping initials, as the app shows a matter's team. */
28
+ export declare const PreviewAvatarStack: ({ people }: {
29
+ people: readonly PreviewPerson[];
30
+ }) => React.JSX.Element;
31
+ export declare const PreviewAvatar: ({ person }: {
32
+ person: PreviewPerson;
33
+ }) => React.JSX.Element;
34
+ //#endregion
@@ -0,0 +1,42 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cn } from "@stll/ui/utils";
3
+ import { ReviewAuthorAvatar } from "@stll/ui/review-author-avatar";
4
+ //#region src/feature-previews/surface.tsx
5
+ /**
6
+ * The window every scene draws in: a title row and a clipped body that fills
7
+ * whatever box the host gives it. Decorative, so it is hidden from assistive
8
+ * technology and takes no pointer input; the host names what it illustrates.
9
+ */
10
+ const FeaturePreviewSurface = ({ icon, title, meta, className, children }) => /* @__PURE__ */ jsxs("div", {
11
+ "aria-hidden": true,
12
+ className: cn("bg-background text-foreground pointer-events-none flex size-full min-h-0 flex-col overflow-hidden rounded-lg border text-start text-xs select-none", className),
13
+ "data-slot": "feature-preview",
14
+ children: [/* @__PURE__ */ jsxs("div", {
15
+ className: "flex h-10 shrink-0 items-center gap-2 border-b px-3",
16
+ children: [
17
+ icon,
18
+ /* @__PURE__ */ jsx("span", {
19
+ className: "min-w-0 truncate text-sm font-medium",
20
+ children: title
21
+ }),
22
+ meta === void 0 ? null : /* @__PURE__ */ jsx("span", {
23
+ className: "text-muted-foreground ms-auto flex shrink-0 items-center gap-2",
24
+ children: meta
25
+ })
26
+ ]
27
+ }), /* @__PURE__ */ jsx("div", {
28
+ className: "relative min-h-0 flex-1 overflow-hidden",
29
+ children
30
+ })]
31
+ });
32
+ /** A row of overlapping initials, as the app shows a matter's team. */
33
+ const PreviewAvatarStack = ({ people }) => /* @__PURE__ */ jsx("span", {
34
+ className: "flex gap-1",
35
+ children: people.map((person) => /* @__PURE__ */ jsx(PreviewAvatar, { person }, person.name))
36
+ });
37
+ const PreviewAvatar = ({ person }) => /* @__PURE__ */ jsx(ReviewAuthorAvatar, {
38
+ className: "size-5 shrink-0",
39
+ name: person.name
40
+ });
41
+ //#endregion
42
+ export { FeaturePreviewSurface, PreviewAvatar, PreviewAvatarStack };
@@ -0,0 +1,35 @@
1
+ import { PreviewMatter } from "./mock-data.js";
2
+ import { FeaturePreviewProps } from "./surface.js";
3
+ //#region src/feature-previews/time-tracking-preview.d.ts
4
+ export type TimeTrackingPreviewLabels = {
5
+ /** Title of the time-tracking surface. */
6
+ time: string;
7
+ /** Label of the day's total row. */
8
+ summary: string;
9
+ };
10
+ export type TimeTrackingPreviewEntry = {
11
+ /** `HH:MM`; `start` is unique within the day. */
12
+ start: string;
13
+ end: string;
14
+ minutes: number;
15
+ description: string;
16
+ matter: PreviewMatter;
17
+ };
18
+ /** The day's content: the running timer (its elapsed time frozen, since a
19
+ * static scene shows one moment) and the entries already recorded. The
20
+ * day's total is computed. */
21
+ export type TimeTrackingPreviewData = {
22
+ /** `YYYY-MM-DD`. */
23
+ day: string;
24
+ running: {
25
+ description: string;
26
+ matter: PreviewMatter;
27
+ elapsedMinutes: number;
28
+ };
29
+ /** In the order they were worked. */
30
+ entries: readonly TimeTrackingPreviewEntry[];
31
+ };
32
+ /** A running timer on a matter above the day's recorded entries and their
33
+ * total. */
34
+ export declare const TimeTrackingPreview: ({ labels, locale, data: { day, running, entries }, className }: FeaturePreviewProps<TimeTrackingPreviewLabels, TimeTrackingPreviewData>) => import("react").JSX.Element;
35
+ //#endregion
@@ -0,0 +1,135 @@
1
+ import { formatPreviewDate, formatPreviewDuration, formatPreviewTime } from "./format.js";
2
+ import { PREVIEW_MATTERS } from "./mock-data.js";
3
+ import { FeaturePreviewSurface } from "./surface.js";
4
+ import { ClockIcon, SquareIcon } from "@stll/ui/icons";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { MatterIcon } from "@stll/ui/matter-icon";
7
+ //#region src/feature-previews/time-tracking-preview.tsx
8
+ /** A running timer on a matter above the day's recorded entries and their
9
+ * total. */
10
+ const TimeTrackingPreview = ({ labels, locale, data: { day, running, entries } = DEFAULT_DATA, className }) => {
11
+ const totalMinutes = entries.reduce((sum, entry) => sum + entry.minutes, 0) + running.elapsedMinutes;
12
+ return /* @__PURE__ */ jsx(FeaturePreviewSurface, {
13
+ className,
14
+ icon: /* @__PURE__ */ jsx(ClockIcon, { className: "text-muted-foreground size-4 shrink-0" }),
15
+ meta: /* @__PURE__ */ jsx("span", {
16
+ className: "whitespace-nowrap",
17
+ children: formatPreviewDate({
18
+ locale,
19
+ date: day,
20
+ options: {
21
+ weekday: "long",
22
+ day: "numeric",
23
+ month: "long"
24
+ }
25
+ })
26
+ }),
27
+ title: labels.time,
28
+ children: /* @__PURE__ */ jsxs("div", {
29
+ className: "flex h-full min-h-0 flex-col gap-3 p-3",
30
+ children: [
31
+ /* @__PURE__ */ jsxs("div", {
32
+ className: "bg-card flex min-w-0 items-center gap-3 rounded-lg border p-3 shadow-xs",
33
+ children: [
34
+ /* @__PURE__ */ jsxs("span", {
35
+ className: "relative flex size-2 shrink-0",
36
+ children: [/* @__PURE__ */ jsx("span", { className: "bg-success absolute inset-0 rounded-full opacity-60 motion-safe:animate-ping" }), /* @__PURE__ */ jsx("span", { className: "bg-success relative size-2 rounded-full" })]
37
+ }),
38
+ /* @__PURE__ */ jsxs("div", {
39
+ className: "flex min-w-0 flex-1 flex-col gap-1",
40
+ children: [/* @__PURE__ */ jsx("span", {
41
+ className: "truncate text-sm font-medium",
42
+ children: running.description
43
+ }), /* @__PURE__ */ jsx(PreviewMatterLabel, { matter: running.matter })]
44
+ }),
45
+ /* @__PURE__ */ jsx("span", {
46
+ className: "shrink-0 text-xl font-medium tabular-nums",
47
+ children: formatPreviewDuration(locale, running.elapsedMinutes)
48
+ }),
49
+ /* @__PURE__ */ jsx("span", {
50
+ className: "bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded-md",
51
+ children: /* @__PURE__ */ jsx(SquareIcon, { className: "size-3 fill-current" })
52
+ })
53
+ ]
54
+ }),
55
+ /* @__PURE__ */ jsx("ul", {
56
+ className: "flex min-h-0 flex-col divide-y",
57
+ children: entries.map((entry) => /* @__PURE__ */ jsxs("li", {
58
+ className: "flex min-w-0 items-center gap-3 py-2",
59
+ children: [
60
+ /* @__PURE__ */ jsxs("span", {
61
+ className: "text-muted-foreground w-24 shrink-0 whitespace-nowrap tabular-nums",
62
+ children: [
63
+ formatPreviewTime(locale, entry.start),
64
+ "–",
65
+ formatPreviewTime(locale, entry.end)
66
+ ]
67
+ }),
68
+ /* @__PURE__ */ jsxs("div", {
69
+ className: "flex min-w-0 flex-1 flex-col gap-0.5",
70
+ children: [/* @__PURE__ */ jsx("span", {
71
+ className: "truncate",
72
+ children: entry.description
73
+ }), /* @__PURE__ */ jsx(PreviewMatterLabel, { matter: entry.matter })]
74
+ }),
75
+ /* @__PURE__ */ jsx("span", {
76
+ className: "shrink-0 tabular-nums",
77
+ children: formatPreviewDuration(locale, entry.minutes)
78
+ })
79
+ ]
80
+ }, entry.start))
81
+ }),
82
+ /* @__PURE__ */ jsxs("div", {
83
+ className: "flex items-center justify-between border-t pt-2 text-sm font-semibold",
84
+ children: [/* @__PURE__ */ jsx("span", { children: labels.summary }), /* @__PURE__ */ jsx("span", {
85
+ className: "tabular-nums",
86
+ children: formatPreviewDuration(locale, totalMinutes)
87
+ })]
88
+ })
89
+ ]
90
+ })
91
+ });
92
+ };
93
+ const PreviewMatterLabel = ({ matter }) => /* @__PURE__ */ jsxs("span", {
94
+ className: "text-muted-foreground flex min-w-0 items-center gap-1.5",
95
+ children: [/* @__PURE__ */ jsx(MatterIcon, {
96
+ className: "size-3 shrink-0",
97
+ matter
98
+ }), /* @__PURE__ */ jsx("span", {
99
+ className: "truncate",
100
+ children: matter.name
101
+ })]
102
+ });
103
+ const DEFAULT_DATA = {
104
+ day: "2026-10-19",
105
+ running: {
106
+ description: "Prepare witness statements",
107
+ matter: PREVIEW_MATTERS.cargo,
108
+ elapsedMinutes: 84
109
+ },
110
+ entries: [
111
+ {
112
+ start: "08:30",
113
+ end: "09:15",
114
+ minutes: 45,
115
+ description: "Review disclosure list",
116
+ matter: PREVIEW_MATTERS.cargo
117
+ },
118
+ {
119
+ start: "09:30",
120
+ end: "11:00",
121
+ minutes: 90,
122
+ description: "Mark up share purchase agreement",
123
+ matter: PREVIEW_MATTERS.acquisition
124
+ },
125
+ {
126
+ start: "11:15",
127
+ end: "11:45",
128
+ minutes: 30,
129
+ description: "Call with landlord's solicitors",
130
+ matter: PREVIEW_MATTERS.lease
131
+ }
132
+ ]
133
+ };
134
+ //#endregion
135
+ export { TimeTrackingPreview };