@heyramzi/ui 2.23.0 → 2.24.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 (123) hide show
  1. package/COMPONENTS.md +3 -6
  2. package/dist/ai/confirmation/index.js +3 -6
  3. package/dist/ai/index.js +1 -5
  4. package/dist/charts/dashboard/index.js +1 -3
  5. package/dist/charts/primitives/chart-utils.js +14 -0
  6. package/dist/components/chip/index.js +1 -0
  7. package/dist/components/language-flag/flags.js +5 -0
  8. package/dist/components/language-flag/index.js +1 -0
  9. package/dist/components/list-page/index.js +1 -3
  10. package/dist/components/work-parts/index.js +1 -1
  11. package/dist/editor/doc-blocks.js +2 -1
  12. package/dist/effects/animated-beam/path-calculator.svelte.js +13 -6
  13. package/dist/lib/dates.js +41 -0
  14. package/dist/lib/panel.js +2 -2
  15. package/dist/lib/tone.js +13 -0
  16. package/dist/record/format.js +2 -18
  17. package/dist/record/logic.js +3 -6
  18. package/dist/status/index.js +1 -1
  19. package/dist/work/dates.js +2 -3
  20. package/dist/work/detail/detail-types.js +0 -8
  21. package/dist/work/docs/edited.js +8 -7
  22. package/dist/work/inbox/inbox-logic.js +5 -20
  23. package/dist/work/index.js +0 -2
  24. package/dist/work/time/duration.js +2 -3
  25. package/package.json +1 -21
  26. package/src/ai/confirmation/Confirmation.svelte +2 -2
  27. package/src/ai/confirmation/{ConfirmationRequest.svelte → ConfirmationState.svelte} +6 -3
  28. package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
  29. package/src/ai/confirmation/confirmation-parts.test.svelte +17 -0
  30. package/src/ai/confirmation/confirmation-parts.test.ts +23 -0
  31. package/src/ai/confirmation/index.ts +6 -21
  32. package/src/ai/index.ts +3 -13
  33. package/src/ai/typing-dots/TypingDots.svelte +32 -12
  34. package/src/ai/typing-dots/typing-dots.test.ts +36 -0
  35. package/src/canvas/ui/canvas-ui.css +12 -0
  36. package/src/charts/README.md +2 -2
  37. package/src/charts/area-chart.svelte +3 -10
  38. package/src/charts/bar-chart.svelte +4 -12
  39. package/src/charts/dashboard/data-table.svelte +21 -21
  40. package/src/charts/dashboard/index.ts +1 -3
  41. package/src/charts/dashboard/rating-chip.svelte +9 -11
  42. package/src/charts/dashboard/stat-tile.svelte +3 -4
  43. package/src/charts/line-chart.svelte +3 -10
  44. package/src/charts/primitives/chart-utils.test.ts +17 -0
  45. package/src/charts/primitives/chart-utils.ts +16 -0
  46. package/src/charts/time-series-chart.svelte +8 -4
  47. package/src/components/chip/count-chip.svelte +35 -0
  48. package/src/components/chip/index.ts +1 -0
  49. package/src/components/data-table/data-table.ts +1 -1
  50. package/src/components/data-table/row-context-menu.svelte +3 -4
  51. package/src/components/data-table/sortable-head.svelte +45 -10
  52. package/src/components/language-flag/flags.ts +5 -0
  53. package/src/components/language-flag/index.ts +1 -0
  54. package/src/components/language-flag/language-flag.svelte +3 -11
  55. package/src/components/list-page/empty-state.svelte +90 -12
  56. package/src/components/list-page/filter-popover.svelte +2 -5
  57. package/src/components/list-page/grouped-list.svelte +2 -4
  58. package/src/components/list-page/index.ts +1 -3
  59. package/src/components/section-header/section-header.svelte +2 -6
  60. package/src/components/segmented/segment.svelte +13 -19
  61. package/src/components/segmented/segmented-filter.svelte +1 -2
  62. package/src/components/table-view/table-view.svelte +31 -45
  63. package/src/components/table-view/table-view.test.ts +6 -3
  64. package/src/components/work-parts/group-head.svelte +1 -1
  65. package/src/components/work-parts/index.ts +1 -1
  66. package/src/editor/doc-blocks.ts +2 -1
  67. package/src/effects/animated-beam/path-calculator.svelte.ts +19 -5
  68. package/src/effects/marquee/Marquee.css +6 -3
  69. package/src/effects/marquee/Marquee.svelte +6 -2
  70. package/src/inbox/thread-inbox.svelte +6 -4
  71. package/src/lib/dates.test.ts +63 -0
  72. package/src/lib/dates.ts +63 -0
  73. package/src/lib/panel.svelte +116 -0
  74. package/src/lib/panel.ts +2 -2
  75. package/src/lib/tone.ts +15 -0
  76. package/src/panel/side-panel.svelte +4 -4
  77. package/src/record/format.ts +3 -15
  78. package/src/record/logic.ts +2 -6
  79. package/src/record/record-stats.svelte +4 -3
  80. package/src/record/steps-rail.svelte +7 -4
  81. package/src/status/index.ts +1 -1
  82. package/src/status/status-pill.svelte +2 -8
  83. package/src/thread/thread.svelte +3 -6
  84. package/src/work/README.md +0 -1
  85. package/src/work/dates.ts +3 -3
  86. package/src/work/demo.svelte +9 -1
  87. package/src/work/detail/detail-demo.svelte +30 -27
  88. package/src/work/detail/detail-types.ts +1 -29
  89. package/src/work/detail/task-detail.svelte +18 -17
  90. package/src/work/docs/doc-menu.svelte +2 -1
  91. package/src/work/docs/edited.ts +4 -7
  92. package/src/work/inbox/inbox-card.svelte +4 -9
  93. package/src/work/inbox/inbox-logic.ts +4 -16
  94. package/src/work/index.ts +1 -3
  95. package/src/work/time/duration.ts +2 -3
  96. package/dist/ai/thinking-dots/index.js +0 -3
  97. package/dist/components/icon/index.js +0 -1
  98. package/dist/components/locale-switch/index.js +0 -1
  99. package/dist/icons/trades.js +0 -49
  100. package/dist/status/pill-tint.js +0 -13
  101. package/dist/work/presets/sales.js +0 -39
  102. package/src/ai/confirmation/ConfirmationAccepted.svelte +0 -23
  103. package/src/ai/confirmation/ConfirmationFailed.svelte +0 -23
  104. package/src/ai/confirmation/ConfirmationRejected.svelte +0 -23
  105. package/src/ai/thinking-dots/ThinkingDots.svelte +0 -42
  106. package/src/ai/thinking-dots/index.ts +0 -5
  107. package/src/charts/dashboard/dashboard-empty.svelte +0 -55
  108. package/src/charts/dashboard/data-panel.svelte +0 -81
  109. package/src/charts/dashboard/data-sort-head.svelte +0 -102
  110. package/src/components/icon/icon.svelte +0 -23
  111. package/src/components/icon/index.ts +0 -1
  112. package/src/components/list-page/detail-empty.svelte +0 -45
  113. package/src/components/list-page/detail-panel.svelte +0 -82
  114. package/src/components/list-page/rail-empty.svelte +0 -44
  115. package/src/components/locale-switch/index.ts +0 -1
  116. package/src/components/locale-switch/locale-switch.svelte +0 -59
  117. package/src/components/work-parts/count-chip.svelte +0 -20
  118. package/src/icons/TradeIcon.svelte +0 -16
  119. package/src/icons/trades.ts +0 -89
  120. package/src/status/pill-tint.ts +0 -16
  121. package/src/work/detail/task-property.svelte +0 -17
  122. package/src/work/detail/task-thread.svelte +0 -221
  123. package/src/work/presets/sales.ts +0 -16
@@ -1,55 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import type { Snippet } from "svelte";
6
- import { cn } from "../../lib/utils.js";
7
- import type { PhosphorIcon } from "../../lib/icon-types.js";
8
-
9
- // =========================
10
- // TYPES
11
- // =========================
12
- interface Props {
13
- // Not list-page's `EmptyState`: that one takes exactly one action, and a
14
- // dashboard can be empty for reasons (nothing connected, nothing synced)
15
- // that need two buttons.
16
-
17
- // The dashed edge marks truly empty, not loading.
18
- icon?: PhosphorIcon;
19
- title: string;
20
- description?: string;
21
- /** One or two buttons. Omit where there is nothing the reader can do. */
22
- actions?: Snippet;
23
- class?: string;
24
- }
25
-
26
- // =========================
27
- // PROPS
28
- // =========================
29
- let { icon: Icon, title, description, actions, class: className }: Props = $props();
30
- </script>
31
-
32
- <!-- =========================== -->
33
- <!-- MARKUP -->
34
- <!-- =========================== -->
35
- <div
36
- class={cn(
37
- "rounded-xl border-2 border-dashed border-border bg-transparent p-10 text-center",
38
- className,
39
- )}
40
- >
41
- <div class="mx-auto flex max-w-md flex-col items-center gap-4">
42
- {#if Icon}
43
- <Icon class="size-10 text-muted-foreground" aria-hidden="true" />
44
- {/if}
45
- <div class="space-y-1.5">
46
- <h2 class="font-heading text-lg font-semibold">{title}</h2>
47
- {#if description}
48
- <p class="text-sm text-muted-foreground">{description}</p>
49
- {/if}
50
- </div>
51
- {#if actions}
52
- <div class="flex flex-wrap justify-center gap-2">{@render actions()}</div>
53
- {/if}
54
- </div>
55
- </div>
@@ -1,81 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import type { Snippet } from "svelte";
6
- import { cn } from "../../lib/utils.js";
7
- import { panelHeaderClass, panelShellClass } from "../../lib/panel.js";
8
- import { SectionLabel } from "../../components/section-header/index.js";
9
- import type { PhosphorIcon } from "../../lib/icon-types.js";
10
-
11
- // =========================
12
- // TYPES
13
- // =========================
14
- interface Props {
15
- /** What this section of the dashboard is. Drawn as the app's one panel label. */
16
- label: string;
17
- labelIcon?: PhosphorIcon;
18
- /**
19
- * A row count, total or freshness stamp. It's a prop, not an action,
20
- * because hand-placed hints ended up at three type sizes on one screen.
21
- */
22
- hint?: string;
23
- /** Controls that act on this panel. Pinned right, after the hint. */
24
- actions?: Snippet;
25
- /** A line under the label, for the scope a number was measured over. */
26
- sublabel?: string;
27
- /** Off for a body that supplies its own padding: a table, a full-bleed list. */
28
- pad?: boolean;
29
- /**
30
- * The body scrolls sideways rather than squeezing. For a wide table, which is
31
- * every table on a dashboard once the window is narrower than its columns.
32
- */
33
- scroll?: boolean;
34
- children: Snippet;
35
- class?: string;
36
- bodyClass?: string;
37
- }
38
-
39
- // =========================
40
- // PROPS
41
- // =========================
42
- let {
43
- label,
44
- labelIcon,
45
- hint,
46
- actions,
47
- sublabel,
48
- pad = true,
49
- scroll = false,
50
- children,
51
- class: className,
52
- bodyClass,
53
- }: Props = $props();
54
- </script>
55
-
56
- <!-- =========================== -->
57
- <!-- MARKUP -->
58
- <!-- =========================== -->
59
- <section class={cn(panelShellClass, className)}>
60
- <div class={panelHeaderClass}>
61
- <div class="flex min-w-0 flex-col gap-0.5">
62
- <SectionLabel icon={labelIcon}>{label}</SectionLabel>
63
- {#if sublabel}
64
- <span class="truncate text-xs text-muted-foreground">{sublabel}</span>
65
- {/if}
66
- </div>
67
- {#if hint || actions}
68
- <div class="flex min-w-0 shrink-0 items-center gap-3">
69
- {#if hint}
70
- <span class="text-xs tabular-nums text-muted-foreground">{hint}</span>
71
- {/if}
72
- {#if actions}
73
- {@render actions()}
74
- {/if}
75
- </div>
76
- {/if}
77
- </div>
78
- <div class={cn(pad && "p-4", scroll && "overflow-x-auto", bodyClass)}>
79
- {@render children()}
80
- </div>
81
- </section>
@@ -1,102 +0,0 @@
1
- <script lang="ts">
2
- /*
3
- * A column head that sorts the table under it. Sort buttons above a table
4
- * never say which column they rank, and read as filter tabs.
5
- */
6
-
7
- // Every sortable head shows its caret, at 40%, solid on the active column.
8
- // Hover-only makes people hunt, and it shifts the label under the pointer.
9
-
10
- // No padding of its own: it comes from the skin classes, so heads and cells
11
- // stay in line when a dense table tightens both.
12
-
13
- // =========================
14
- // IMPORTS
15
- // =========================
16
- import type { Snippet } from "svelte";
17
- import { CaretDown, CaretUp } from "phosphor-svelte";
18
- import { cn } from "../../lib/utils.js";
19
- import { dataThClass, dataThNumClass } from "./data-table.js";
20
-
21
- // =========================
22
- // TYPES
23
- // =========================
24
- interface Props {
25
- /** True when the table is currently ranked by this column. */
26
- active: boolean;
27
- /** Which way the active column runs. Ignored while inactive. */
28
- descending: boolean;
29
- /** Called on click and on Enter/Space. Toggling direction is the caller's. */
30
- onSort: () => void;
31
- /** Numbers right-align to their digits, so their caret leads the label. */
32
- numeric?: boolean;
33
- /** The long form of an abbreviated head, as the browser tooltip. */
34
- title?: string;
35
- class?: string;
36
- children: Snippet;
37
- }
38
-
39
- // =========================
40
- // PROPS
41
- // =========================
42
- let {
43
- active,
44
- descending,
45
- onSort,
46
- numeric = false,
47
- title,
48
- class: className,
49
- children,
50
- }: Props = $props();
51
-
52
- // =========================
53
- // DERIVED
54
- // =========================
55
- const ariaSort = $derived(active ? (descending ? "descending" : "ascending") : "none");
56
-
57
- // =========================
58
- // FUNCTIONS
59
- // =========================
60
- function handleKeydown(event: KeyboardEvent) {
61
- if (event.key === "Enter" || event.key === " ") {
62
- event.preventDefault();
63
- onSort();
64
- }
65
- }
66
- </script>
67
-
68
- <!-- =========================== -->
69
- <!-- MARKUP -->
70
- <!-- =========================== -->
71
- <th
72
- scope="col"
73
- {title}
74
- tabindex="0"
75
- aria-sort={ariaSort}
76
- onclick={onSort}
77
- onkeydown={handleKeydown}
78
- class={cn(
79
- numeric ? dataThNumClass : dataThClass,
80
- "cursor-pointer select-none transition-colors hover:text-foreground",
81
- active && "text-foreground",
82
- className,
83
- )}
84
- >
85
- <span class="inline-flex items-center gap-0.5 whitespace-nowrap">
86
- {#if numeric}
87
- {@render caret()}{@render children()}
88
- {:else}
89
- {@render children()}{@render caret()}
90
- {/if}
91
- </span>
92
- </th>
93
-
94
- {#snippet caret()}
95
- {#if active && !descending}
96
- <CaretUp class="size-3 shrink-0" weight="bold" />
97
- {:else if active}
98
- <CaretDown class="size-3 shrink-0" weight="bold" />
99
- {:else}
100
- <CaretDown class="size-3 shrink-0 text-muted-foreground/40" />
101
- {/if}
102
- {/snippet}
@@ -1,23 +0,0 @@
1
- <script lang="ts">
2
- import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
3
-
4
- // =========================
5
- // TYPES
6
- // =========================
7
- interface Props {
8
- icon: PhosphorIcon;
9
- size?: number;
10
- class?: string;
11
- weight?: IconWeight;
12
- }
13
-
14
- // =========================
15
- // PROPS
16
- // =========================
17
- let { icon: IconComp, size = 24, class: className = "", weight }: Props = $props();
18
- </script>
19
-
20
- <!-- =========================== -->
21
- <!-- MARKUP -->
22
- <!-- =========================== -->
23
- <IconComp {size} class={className} {weight} />
@@ -1 +0,0 @@
1
- export { default as Icon } from "./icon.svelte";
@@ -1,45 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import EmptyState from "./empty-state.svelte";
6
- import { cn } from "../../lib/utils.js";
7
- import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
8
-
9
- // =========================
10
- // TYPES
11
- // =========================
12
- interface Props {
13
- // Shown when nothing is picked or the board is empty. It sits on the white
14
- // panel with no frame of its own: a dashed edge drew a box round nothing.
15
- icon?: PhosphorIcon;
16
- iconWeight?: IconWeight;
17
- title: string;
18
- description?: string;
19
- actionLabel?: string;
20
- actionHref?: string;
21
- onAction?: () => void;
22
- class?: string;
23
- }
24
-
25
- // =========================
26
- // PROPS
27
- // =========================
28
- let {
29
- icon,
30
- iconWeight,
31
- title,
32
- description,
33
- actionLabel,
34
- actionHref,
35
- onAction,
36
- class: className,
37
- }: Props = $props();
38
- </script>
39
-
40
- <!-- =========================== -->
41
- <!-- MARKUP -->
42
- <!-- =========================== -->
43
- <div class={cn("flex h-full items-center justify-center rounded-xl bg-card", className)}>
44
- <EmptyState {icon} {iconWeight} {title} {description} {actionLabel} {actionHref} {onAction} />
45
- </div>
@@ -1,82 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import type { Snippet } from "svelte";
6
- import SectionLabel from "../section-header/section-label.svelte";
7
- import { cn } from "../../lib/utils.js";
8
- import { panelHeaderClass, panelShellClass } from "../../lib/panel.js";
9
- import type { PhosphorIcon } from "../../lib/icon-types.js";
10
-
11
- // =========================
12
- // TYPES
13
- // =========================
14
- interface Props {
15
- // One panel per idea in a detail pane. Hand-rolled copies drifted between
16
- // 8px and 12px corners, so rounded-xl lives here once.
17
- label?: string;
18
- labelIcon?: PhosphorIcon;
19
- // A mark the icon set does not carry (a platform logo), drawn ahead of the
20
- // label at the same optical size.
21
- labelLead?: Snippet;
22
- // The right side of the header bar: a count, a hint, a button cluster.
23
- actions?: Snippet;
24
- // WHY: a panel whose body is a media grid or a full-bleed list supplies its
25
- // own padding, so the shell must be able to step out of the way.
26
- pad?: boolean;
27
- // Takes the height the panels above it did not use, inside a `fill` stack.
28
- // One panel per stack, and it is the one the reader works in.
29
- grow?: boolean;
30
- children: Snippet;
31
- class?: string;
32
- bodyClass?: string;
33
- }
34
-
35
- // =========================
36
- // PROPS
37
- // =========================
38
- let {
39
- label,
40
- labelIcon,
41
- labelLead,
42
- actions,
43
- pad = true,
44
- grow = false,
45
- children,
46
- class: className,
47
- bodyClass,
48
- }: Props = $props();
49
-
50
- // =========================
51
- // DERIVED
52
- // =========================
53
- let hasHeader = $derived(Boolean(label || labelLead || actions));
54
- </script>
55
-
56
- <!-- =========================== -->
57
- <!-- MARKUP -->
58
- <!-- =========================== -->
59
- <section class={cn(panelShellClass, grow && "flex min-h-0 flex-1 flex-col", className)}>
60
- {#if hasHeader}
61
- <div class={panelHeaderClass}>
62
- {#if label || labelLead}
63
- <SectionLabel icon={labelIcon}>
64
- {#if labelLead}
65
- {@render labelLead()}
66
- {/if}
67
- {label}
68
- </SectionLabel>
69
- {:else}
70
- <span></span>
71
- {/if}
72
- {#if actions}
73
- <div class="flex min-w-0 flex-wrap items-center justify-end gap-2">
74
- {@render actions()}
75
- </div>
76
- {/if}
77
- </div>
78
- {/if}
79
- <div class={cn(pad && "p-4", grow && "flex min-h-0 flex-1 flex-col", bodyClass)}>
80
- {@render children()}
81
- </div>
82
- </section>
@@ -1,44 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import { cn } from "../../lib/utils.js";
6
- import * as Empty from "../../shadcn/empty/index.js";
7
- import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
8
-
9
- // =========================
10
- // TYPES
11
- // =========================
12
- interface Props {
13
- // Not `EmptyState`, which has a 48px icon and an action. This says a filter
14
- // came back empty, in one sentence that fits a 280px rail.
15
- icon?: PhosphorIcon;
16
- iconWeight?: IconWeight;
17
- iconClass?: string;
18
- title: string;
19
- hint?: string;
20
- class?: string;
21
- }
22
-
23
- // =========================
24
- // PROPS
25
- // =========================
26
- let { icon: Icon, iconWeight, iconClass, title, hint, class: className }: Props = $props();
27
- </script>
28
-
29
- <!-- =========================== -->
30
- <!-- MARKUP -->
31
- <!-- =========================== -->
32
- <Empty.Root class={cn("h-full gap-2 px-6 py-12", className)}>
33
- {#if Icon}
34
- <Icon
35
- weight={iconWeight}
36
- class={cn("size-6", iconClass ?? "text-muted-foreground")}
37
- aria-hidden="true"
38
- />
39
- {/if}
40
- <Empty.Title>{title}</Empty.Title>
41
- {#if hint}
42
- <Empty.Description class="max-w-56 text-xs leading-relaxed">{hint}</Empty.Description>
43
- {/if}
44
- </Empty.Root>
@@ -1 +0,0 @@
1
- export { default as LocaleSwitch } from "./locale-switch.svelte";
@@ -1,59 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import { SegmentedFilter, type TabDef } from "../segmented/index.js";
6
-
7
- // =========================
8
- // TYPES
9
- // =========================
10
- type LocaleOption = {
11
- value: string;
12
- label: string;
13
- flag: string;
14
- };
15
-
16
- interface Props {
17
- // Wraps `SegmentedFilter` so it shares one track with every other strip. It
18
- // only adds each language's flag.
19
- value: string;
20
- onchange: (value: string) => void;
21
- options?: LocaleOption[];
22
- /** Names the control for a screen reader. Without it the group is two bare letters. */
23
- ariaLabel?: string;
24
- class?: string;
25
- }
26
-
27
- // =========================
28
- // PROPS
29
- // =========================
30
- let {
31
- value,
32
- onchange,
33
- options = [
34
- { value: "en", label: "EN", flag: "/flags/US.svg" },
35
- { value: "fr", label: "FR", flag: "/flags/FR.svg" },
36
- ],
37
- ariaLabel = "Language",
38
- class: className,
39
- }: Props = $props();
40
-
41
- // =========================
42
- // DERIVED
43
- // =========================
44
- let segments = $derived<TabDef<string>[]>(
45
- options.map((option) => ({ value: option.value, label: option.label, image: option.flag })),
46
- );
47
- </script>
48
-
49
- <!-- =========================== -->
50
- <!-- MARKUP -->
51
- <!-- =========================== -->
52
- <SegmentedFilter
53
- {value}
54
- onValueChange={onchange}
55
- options={segments}
56
- block={false}
57
- {ariaLabel}
58
- class={className}
59
- />
@@ -1,20 +0,0 @@
1
- <script lang="ts">
2
- import { cn } from "../../lib/utils.js";
3
-
4
- interface Props {
5
- count: number;
6
- class?: string;
7
- }
8
-
9
- let { count, class: className }: Props = $props();
10
- </script>
11
-
12
- <!-- A count beside a heading: a quiet square, never a pill, so it reads as a number and not a status. -->
13
- <span
14
- class={cn(
15
- "grid h-5 min-w-5 shrink-0 place-items-center rounded-sm bg-foreground/5 px-1 text-xs font-medium text-muted-foreground tabular-nums",
16
- className,
17
- )}
18
- >
19
- {count.toLocaleString()}
20
- </span>
@@ -1,16 +0,0 @@
1
- <script lang="ts">
2
- import { tradeIconMap, type TradeSlug } from "./trades.js";
3
-
4
- let {
5
- trade,
6
- size = 14,
7
- stroke = 2,
8
- class: className = "",
9
- }: { trade: TradeSlug; size?: number; stroke?: number; class?: string } = $props();
10
-
11
- let Icon = $derived(tradeIconMap[trade]);
12
- </script>
13
-
14
- {#if Icon}
15
- <Icon {size} {stroke} class={className} aria-hidden="true" focusable="false" />
16
- {/if}
@@ -1,89 +0,0 @@
1
- /**
2
- * Trade icon map. Tabler covers the domain Lucide and Phosphor miss.
3
- */
4
-
5
- /*
6
- * Lucide/Phosphor gap audit (Sept 2026) against the 14 trade pills in the "NOT SURE? TAP WHAT YOU DO" strip. Lucide has `hammer`, `wrench`, `hard-hat`, `bolt`, `brick-wall`,
7
- */
8
-
9
- /*
10
- * `building`, `car`, but no `trowel`, no tiling grid, no `window` cleaning `spray/squeegee`, no `crane`, no dedicated `handyman` toolset. Phosphor has `hammer`, `wrench`, `hard-hat`, `paint-roller`, and also no `trowel`.
11
- */
12
-
13
- /*
14
- * Tabler 3.46 (6184 icons, MIT) has all 14 at a consistent 2px stroke on 24×24: droplet/pipe, bolt, home (roof), tools, key, window,
15
- */
16
-
17
- /*
18
- * car-garage, hammer / wave-saw-tool, helmet, trowel, grid-dots, leaf, car, crane. Svelte 5 port is ` @tabler/icons-svelte-runes` (runes, not the Svelte 4 ` @tabler/icons-svelte`).
19
- */
20
-
21
- import {
22
- IconBolt,
23
- IconCar,
24
- IconCarGarage,
25
- IconCrane,
26
- IconDroplet,
27
- IconGridDots,
28
- IconHammer,
29
- IconHelmet,
30
- IconHome,
31
- IconKey,
32
- IconLeaf,
33
- IconTools,
34
- IconTrowel,
35
- IconWindow,
36
- } from "@tabler/icons-svelte-runes";
37
- import type { Component } from "svelte";
38
-
39
- export type TradeSlug =
40
- | "plumbers"
41
- | "electricians"
42
- | "roofers"
43
- | "handymen"
44
- | "locksmiths"
45
- | "window-cleaners"
46
- | "garages"
47
- | "carpenters"
48
- | "builders"
49
- | "plasterers"
50
- | "tilers"
51
- | "gardeners"
52
- | "mobile-mechanics"
53
- | "construction-companies";
54
-
55
- export const tradeIconMap: Record<TradeSlug, Component> = {
56
- plumbers: IconDroplet,
57
- electricians: IconBolt,
58
- roofers: IconHome,
59
- handymen: IconTools,
60
- locksmiths: IconKey,
61
- "window-cleaners": IconWindow,
62
- garages: IconCarGarage,
63
- carpenters: IconHammer,
64
- builders: IconHelmet,
65
- plasterers: IconTrowel,
66
- tilers: IconGridDots,
67
- gardeners: IconLeaf,
68
- "mobile-mechanics": IconCar,
69
- "construction-companies": IconCrane,
70
- };
71
-
72
- export const tradeLabels: Record<TradeSlug, string> = {
73
- plumbers: "PLUMBERS",
74
- electricians: "ELECTRICIANS",
75
- roofers: "ROOFERS",
76
- handymen: "HANDYMEN",
77
- locksmiths: "LOCKSMITHS",
78
- "window-cleaners": "WINDOW CLEANERS",
79
- garages: "GARAGES",
80
- carpenters: "CARPENTERS",
81
- builders: "BUILDERS",
82
- plasterers: "PLASTERERS",
83
- tilers: "TILERS",
84
- gardeners: "GARDENERS",
85
- "mobile-mechanics": "MOBILE MECHANICS",
86
- "construction-companies": "CONSTRUCTION COMPANIES",
87
- };
88
-
89
- export const tradeSlugs: TradeSlug[] = Object.keys(tradeIconMap) as TradeSlug[];
@@ -1,16 +0,0 @@
1
- import type { Tone } from "../lib/tone.js";
2
-
3
- // A quiet tint and no border: the icon and the text carry the tone, the fill only groups
4
- // them. Neutral reads off the foreground, not `bg-muted`, because muted sits 0.005 L from
5
- // the canvas and vanishes there (tokens board, "what fights the new scale").
6
- // It lives in a .ts module, not status-pill.svelte's module script, so a plain tsc consumer
7
- // can import it: tsc's `*.svelte` shim only knows a default export.
8
- export const PILL_TINT: Record<Tone, string> = {
9
- neutral: "bg-foreground/6 text-muted-foreground",
10
- accent:
11
- "bg-[color-mix(in_oklab,var(--tone-accent,var(--color-primary))_12%,transparent)] text-[color:var(--tone-accent,var(--color-primary))]",
12
- info: "bg-info/12 text-info-800 dark:text-info-400",
13
- success: "bg-success/12 text-success-800 dark:text-success-400",
14
- warning: "bg-warning/14 text-warning-800 dark:text-warning-400",
15
- destructive: "bg-destructive/10 text-destructive-ink",
16
- };
@@ -1,17 +0,0 @@
1
- <script lang="ts">
2
- import type { Component, Snippet } from "svelte";
3
- import PropertyRow from "../../components/work-parts/property-row.svelte";
4
-
5
- interface Props {
6
- label: string;
7
- /** A Phosphor icon before the label, shown when the rail is wide. */
8
- icon?: Component<{ class?: string; "aria-hidden"?: boolean | "true" | "false" }>;
9
- children: Snippet;
10
- }
11
-
12
- let { label, icon, children }: Props = $props();
13
- </script>
14
-
15
- <!-- A labelled row in the rail: icon, label, value. In the narrow chip row the label hides and
16
- names the group for assistive tech. -->
17
- <PropertyRow {label} {icon} layout="responsive">{@render children()}</PropertyRow>