@lapismd/design-core 0.1.0 → 0.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 (124) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/LICENSE.md +3 -3
  3. package/README.md +21 -1
  4. package/package.json +34 -15
  5. package/spec/src/styling-and-themes.md +11 -9
  6. package/src/shared/ai/chat.css +25 -1
  7. package/src/shared/ai/chat.ts +12 -0
  8. package/src/shared/ai/composer-input/ComposerInput.svelte +3 -2
  9. package/src/shared/ai/context.svelte.ts +2 -0
  10. package/src/shared/ai/experimental/reaction-bar/ReactionBar.svelte +1 -1
  11. package/src/shared/ai/layout/Layout.svelte +116 -10
  12. package/src/shared/ai/message-list/MessageList.svelte +123 -43
  13. package/src/shared/ai/message-list/MessageListHistoryFixture.svelte +112 -0
  14. package/src/shared/ai/message-list/MessageListLegacyFixture.svelte +52 -0
  15. package/src/shared/ai/message-list/VirtualizedMessageRows.svelte +299 -0
  16. package/src/shared/ai/message-list/position.ts +7 -0
  17. package/src/shared/ai/new-messages.svelte.ts +24 -5
  18. package/src/shared/ai/stream-scroll.svelte.ts +135 -6
  19. package/src/shared/ai/tool-call-detail/ToolCallDetail.svelte +71 -0
  20. package/src/shared/ai/tool-call-detail/tool-call-detail.ts +209 -0
  21. package/src/shared/ai/tool-calls/ToolCalls.svelte +1 -1
  22. package/src/shared/ai/trigger-menu.ts +2 -3
  23. package/src/shared/diff/core/merge/actions.ts +4 -3
  24. package/src/shared/diff/core/merge/render-model.ts +14 -10
  25. package/src/shared/diff/file-diff/FileDiff.css +68 -1
  26. package/src/shared/diff/file-diff/FileDiff.svelte +27 -14
  27. package/src/shared/diff/file-diff/FileDiffComposer.svelte +98 -16
  28. package/src/shared/diff/file-diff/index.ts +2 -0
  29. package/src/shared/diff/file-diff/types.ts +13 -0
  30. package/src/shared/diff/file-listing/FileListingViewModeToggle.css +42 -5
  31. package/src/shared/diff/index.ts +2 -0
  32. package/src/shared/diff/merge-editor/MergeEditor.svelte +2 -7
  33. package/src/shared/filter/filter-query/filters.ts +2 -2
  34. package/src/shared/filter/filter-query/parser.ts +9 -8
  35. package/src/shared/filter/power-search/PowerSearch.svelte +1 -1
  36. package/src/shared/filter/search-filter-bar/SearchFilterBar.svelte +2 -2
  37. package/src/shared/filter/search-filter-bar/SearchFilterInput.svelte +1 -1
  38. package/src/shared/filter/search-filter-bar/SearchFilterPredicateEditor.svelte +1 -1
  39. package/src/shared/filter/search-filter-bar/search-filter-syntax.ts +1 -1
  40. package/src/shared/forms/autocomplete-input/AutocompleteInput.svelte +19 -7
  41. package/src/shared/forms/confirm-button/ConfirmButton.svelte +38 -0
  42. package/src/shared/forms/confirm-button/ConfirmButtonExample.svelte +14 -0
  43. package/src/shared/forms/confirm-button/index.ts +1 -0
  44. package/src/shared/forms/confirm-dialog/ConfirmDialog.css +21 -0
  45. package/src/shared/forms/confirm-dialog/ConfirmDialog.svelte +116 -0
  46. package/src/shared/forms/confirm-dialog/ConfirmDialogExample.svelte +17 -0
  47. package/src/shared/forms/confirm-dialog/index.ts +2 -0
  48. package/src/shared/forms/dialog-frame/DialogFrame.css +130 -0
  49. package/src/shared/forms/dialog-frame/DialogFrame.svelte +108 -0
  50. package/src/shared/forms/dialog-frame/DialogFrameExample.svelte +37 -0
  51. package/src/shared/forms/dialog-frame/index.ts +6 -0
  52. package/src/shared/forms/dialog.ts +8 -0
  53. package/src/shared/forms/index.ts +11 -0
  54. package/src/shared/shadcn/button/button.svelte +4 -0
  55. package/src/shared/shadcn/code-block/highlightRanges.ts +10 -6
  56. package/src/shared/shadcn/code-block/syntax/defineSyntaxTheme.ts +4 -2
  57. package/src/shared/shadcn/code-block/tokenizer.ts +3 -3
  58. package/src/shared/shadcn/command/command-dialog.svelte +1 -1
  59. package/src/shared/shadcn/command/command-group.svelte +3 -1
  60. package/src/shared/shadcn/command-view/command-view.svelte +1 -6
  61. package/src/shared/shadcn/dialog/dialog-content.svelte +3 -4
  62. package/src/shared/shadcn/dialog/dialog-portal.svelte +10 -2
  63. package/src/shared/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -691
  64. package/src/shared/shadcn/dropdown-menu/dropdown-menu-content.svelte +691 -0
  65. package/src/shared/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +1 -1
  66. package/src/shared/shadcn/scroll-area/scroll-area.svelte +2 -0
  67. package/src/shared/shadcn/spinner/spinner.svelte +3 -3
  68. package/src/shared/shadcn/tooltip/tooltip-content.svelte +10 -4
  69. package/src/shared/shadcn/tooltip/tooltip.tokens.ts +4 -0
  70. package/src/shared/shell/app-shell/AppShell.css +112 -32
  71. package/src/shared/shell/app-shell/AppShell.demo.css +37 -0
  72. package/src/shared/shell/app-shell/AppShellBodyContent.svelte +5 -1
  73. package/src/shared/shell/app-shell/AppShellBodySidebar.svelte +5 -1
  74. package/src/shared/shell/app-shell/AppShellMain.svelte +12 -1
  75. package/src/shared/shell/app-shell/AppShellMobilePanelSelector.svelte +1 -1
  76. package/src/shared/shell/app-shell/AppShellRoot.svelte +27 -13
  77. package/src/shared/shell/app-shell/AppShellSidebar.svelte +121 -19
  78. package/src/shared/shell/app-shell/AppShellSidebarClose.svelte +17 -17
  79. package/src/shared/shell/app-shell/AppShellSidebarToggle.svelte +38 -58
  80. package/src/shared/shell/app-shell/AppShellSurfaceLayer.svelte +173 -0
  81. package/src/shared/shell/app-shell/AppShellSurfaceLayerBody.svelte +32 -0
  82. package/src/shared/shell/app-shell/AppShellSurfaceLayerHeader.svelte +26 -0
  83. package/src/shared/shell/app-shell/app-shell-context.svelte.ts +6 -3
  84. package/src/shared/shell/app-shell/app-shell-controller.svelte.ts +269 -11
  85. package/src/shared/shell/app-shell/app-shell-sidebar-context.svelte.ts +1 -0
  86. package/src/shared/shell/app-shell/examples/AppShellBodyDemo.svelte +4 -0
  87. package/src/shared/shell/app-shell/examples/AppShellCompleteDemo.svelte +1 -0
  88. package/src/shared/shell/app-shell/examples/AppShellConversationDemo.svelte +1 -0
  89. package/src/shared/shell/app-shell/examples/AppShellExamples.css +624 -0
  90. package/src/shared/shell/app-shell/examples/AppShellFilesSidebarDemo.svelte +1 -0
  91. package/src/shared/shell/app-shell/examples/AppShellMarkdownDocumentDemo.svelte +6 -1
  92. package/src/shared/shell/app-shell/examples/AppShellMarkdownFilesDemo.svelte +1 -0
  93. package/src/shared/shell/app-shell/examples/AppShellModeSwitchDemo.svelte +1 -0
  94. package/src/shared/shell/app-shell/examples/AppShellMultiPanelDemo.svelte +187 -0
  95. package/src/shared/shell/app-shell/examples/AppShellProjectSidebarDemo.svelte +1 -0
  96. package/src/shared/shell/app-shell/examples/AppShellSidebarDemo.svelte +1 -0
  97. package/src/shared/shell/app-shell/examples/AppShellToolbarDemo.svelte +1 -0
  98. package/src/shared/shell/app-shell/index.ts +14 -0
  99. package/src/shared/shell/shell.tokens.css +2 -0
  100. package/src/shared/shell/shell.tokens.ts +5 -0
  101. package/src/shared/workspace/menu/WorkspaceContextMenuItems.svelte +2 -1
  102. package/src/shared/workspace/menu/WorkspaceMenuItems.svelte +2 -1
  103. package/src/shared/workspace/problems/ProblemsQuickFix.svelte +1 -4
  104. package/src/shared/workspace/settings/AppSettingsContent.svelte +6 -0
  105. package/src/shared/workspace/settings/AppSettingsNavigation.svelte +7 -0
  106. package/src/shared/workspace/settings/CopyableValue.svelte +81 -0
  107. package/src/shared/workspace/settings/DeclarativeSettingsSection.svelte +20 -0
  108. package/src/shared/workspace/settings/WorkspaceSettingField.svelte +110 -41
  109. package/src/shared/workspace/settings/WorkspaceSettingMultiSelect.svelte +7 -6
  110. package/src/shared/workspace/settings/WorkspaceSettingObjectTable.svelte +187 -23
  111. package/src/shared/workspace/settings/WorkspaceSettingSelect.svelte +29 -29
  112. package/src/shared/workspace/settings/WorkspaceSettings.css +190 -4
  113. package/src/shared/workspace/settings/WorkspaceSettingsStoryCustomField.svelte +2 -2
  114. package/src/shared/workspace/settings/WorkspaceSettingsStoryCustomPage.svelte +13 -0
  115. package/src/shared/workspace/settings/WorkspaceSettingsSurface.svelte +50 -0
  116. package/src/shared/workspace/settings/configuration.ts +5 -0
  117. package/src/shared/workspace/settings/copyable-value.ts +10 -0
  118. package/src/shared/workspace/settings/index.ts +5 -0
  119. package/src/shared/workspace/settings/settings-controller.svelte.ts +349 -15
  120. package/src/shared/workspace/settings/types.ts +90 -8
  121. package/src/storybook/app-shell-reference/AppShellTwoExpandedSidebarsReference.css +25 -0
  122. package/src/storybook/app-shell-reference/AppShellTwoExpandedSidebarsReference.svelte +48 -0
  123. package/src/storybook/app-shell-reference/index.ts +1 -0
  124. package/src/theme.css +10 -0
@@ -0,0 +1,187 @@
1
+ <script lang="ts">
2
+ import { Button } from "../../../shadcn/button/index.js";
3
+ import type {
4
+ AppShellController,
5
+ AppShellDisplayMode,
6
+ AppShellSidebarController,
7
+ } from "../app-shell-controller.svelte.js";
8
+ import { AppShell } from "../index.js";
9
+ import "./AppShellExamples.css";
10
+
11
+ let {
12
+ controller,
13
+ overview,
14
+ activity,
15
+ displayMode = "desktop",
16
+ frameClass,
17
+ direction = "ltr",
18
+ }: {
19
+ controller: AppShellController;
20
+ overview: AppShellSidebarController;
21
+ activity: AppShellSidebarController;
22
+ displayMode?: AppShellDisplayMode;
23
+ frameClass?: string;
24
+ direction?: "ltr" | "rtl";
25
+ } = $props();
26
+
27
+ let surfaceOpen = $state(false);
28
+ let surfaceTrigger = $state<HTMLButtonElement | null>(null);
29
+ </script>
30
+
31
+ <div
32
+ class={["ui-shell-story-frame", "ui-shell-story-multi-panel", frameClass]
33
+ .filter(Boolean)
34
+ .join(" ")}
35
+ dir={direction}
36
+ >
37
+ <AppShell.Root
38
+ {controller}
39
+ {displayMode}
40
+ mobileBreakpoint={760}
41
+ desktopMinMainWidth={440}
42
+ class="ui-shell-story-surface"
43
+ >
44
+ <AppShell.Sidebar side="left" label="Navigation" resizable>
45
+ <AppShell.Sidebar.Header>
46
+ <strong>Workspace</strong>
47
+ </AppShell.Sidebar.Header>
48
+ <AppShell.Sidebar.Body>
49
+ <nav class="ui-shell-story-multi-panel__nav" aria-label="Workspace">
50
+ <button type="button" data-active>Inbox</button>
51
+ <button type="button">Projects</button>
52
+ <button type="button">Archive</button>
53
+ </nav>
54
+ </AppShell.Sidebar.Body>
55
+ </AppShell.Sidebar>
56
+
57
+ <AppShell.Main>
58
+ <AppShell.Toolbar class="ui-shell-story-multi-panel__toolbar">
59
+ <AppShell.Sidebar.Toggle side="left" sidebarName="navigation" />
60
+ <strong>Project updates</strong>
61
+ <span></span>
62
+ <AppShell.Sidebar.Toggle side="right" sidebarName="details" />
63
+ <AppShell.Sidebar.Toggle
64
+ side="right"
65
+ sidebarController={overview}
66
+ sidebarName="overview"
67
+ />
68
+ </AppShell.Toolbar>
69
+ <AppShell.Body label="Project workspace">
70
+ <article class="ui-shell-story-multi-panel__content">
71
+ <p>Project workspace</p>
72
+ <h1>Composable same-side panels</h1>
73
+ <p>
74
+ The main surface stays mounted while inspectors, overview rails, and
75
+ structural layers respond to the available width.
76
+ </p>
77
+ <button type="button">Draft update</button>
78
+ </article>
79
+ </AppShell.Body>
80
+ </AppShell.Main>
81
+
82
+ <AppShell.Sidebar
83
+ side="right"
84
+ label="Details"
85
+ constraintPriority={0}
86
+ constrainedPresentation="replace-main"
87
+ closeable
88
+ >
89
+ <AppShell.Sidebar.Header>
90
+ <strong>Details</strong>
91
+ <AppShell.Sidebar.Close />
92
+ </AppShell.Sidebar.Header>
93
+ <AppShell.Sidebar.Body>
94
+ <div class="ui-shell-story-multi-panel__panel-copy">
95
+ <p>Owner</p>
96
+ <strong>Design systems</strong>
97
+ <p>Status</p>
98
+ <strong>In review</strong>
99
+ </div>
100
+ </AppShell.Sidebar.Body>
101
+ </AppShell.Sidebar>
102
+
103
+ <AppShell.Sidebar
104
+ side="right"
105
+ sidebarController={overview}
106
+ label="Overview rail"
107
+ variant="rail"
108
+ constraintPriority={100}
109
+ resizable
110
+ >
111
+ <AppShell.Sidebar.Body>
112
+ <nav class="ui-shell-story-multi-panel__rail" aria-label="Overview">
113
+ <button type="button"><span>Reviews</span><small>4</small></button>
114
+ <button type="button"><span>People</span><small>12</small></button>
115
+ </nav>
116
+ </AppShell.Sidebar.Body>
117
+ </AppShell.Sidebar>
118
+
119
+ <AppShell.Sidebar
120
+ side="right"
121
+ sidebarController={activity}
122
+ label="Activity rail"
123
+ variant="rail"
124
+ constraintPriority={100}
125
+ resizable
126
+ >
127
+ <AppShell.Sidebar.Body>
128
+ <nav class="ui-shell-story-multi-panel__rail" aria-label="Activity">
129
+ <button type="button"><span>Tasks</span><small>7</small></button>
130
+ <button type="button"><span>Commits</span><small>18</small></button>
131
+ <Button
132
+ bind:ref={surfaceTrigger}
133
+ variant="outline"
134
+ size="sm"
135
+ onclick={() => (surfaceOpen = true)}
136
+ >
137
+ Open reviews
138
+ </Button>
139
+ </nav>
140
+ </AppShell.Sidebar.Body>
141
+ </AppShell.Sidebar>
142
+
143
+ <AppShell.SurfaceLayer
144
+ open={surfaceOpen}
145
+ label="Release reviews"
146
+ coverPanelIds={["right"]}
147
+ suspendPanelIds={["overview"]}
148
+ returnFocus={surfaceTrigger}
149
+ dismissLabel="Close reviews"
150
+ onDismiss={() => (surfaceOpen = false)}
151
+ >
152
+ <AppShell.SurfaceLayer.Header>
153
+ <strong>Release reviews</strong>
154
+ <Button
155
+ variant="ghost"
156
+ size="icon-sm"
157
+ aria-label="Close release reviews"
158
+ onclick={() => (surfaceOpen = false)}
159
+ >
160
+ <svg
161
+ xmlns="http://www.w3.org/2000/svg"
162
+ viewBox="0 0 24 24"
163
+ fill="none"
164
+ stroke="currentColor"
165
+ stroke-width="2"
166
+ stroke-linecap="round"
167
+ aria-hidden="true"
168
+ >
169
+ <path d="M18 6 6 18M6 6l12 12"></path>
170
+ </svg>
171
+ </Button>
172
+ </AppShell.SurfaceLayer.Header>
173
+ <AppShell.SurfaceLayer.Body>
174
+ <div class="ui-shell-story-multi-panel__reviews">
175
+ <article>
176
+ <strong>History 0.1.3</strong>
177
+ <span>Pending review</span>
178
+ </article>
179
+ <article>
180
+ <strong>Markdown 0.1.5</strong>
181
+ <span>Approved</span>
182
+ </article>
183
+ </div>
184
+ </AppShell.SurfaceLayer.Body>
185
+ </AppShell.SurfaceLayer>
186
+ </AppShell.Root>
187
+ </div>
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import "./AppShellExamples.css";
2
3
  import BoxesIcon from "@lucide/svelte/icons/boxes";
3
4
  import FolderKanbanIcon from "@lucide/svelte/icons/folder-kanban";
4
5
  import NotebookTabsIcon from "@lucide/svelte/icons/notebook-tabs";
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import "./AppShellExamples.css";
2
3
  import FileTextIcon from "@lucide/svelte/icons/file-text";
3
4
  import FilesIcon from "@lucide/svelte/icons/files";
4
5
  import InfoIcon from "@lucide/svelte/icons/info";
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import "./AppShellExamples.css";
2
3
  import { AppShell } from "../index.js";
3
4
 
4
5
  let {
@@ -10,6 +10,9 @@ import AppShellSidebarClose from "./AppShellSidebarClose.svelte";
10
10
  import AppShellSidebarFooter from "./AppShellSidebarFooter.svelte";
11
11
  import AppShellSidebarHeader from "./AppShellSidebarHeader.svelte";
12
12
  import AppShellSidebarToggle from "./AppShellSidebarToggle.svelte";
13
+ import AppShellSurfaceLayer from "./AppShellSurfaceLayer.svelte";
14
+ import AppShellSurfaceLayerBody from "./AppShellSurfaceLayerBody.svelte";
15
+ import AppShellSurfaceLayerHeader from "./AppShellSurfaceLayerHeader.svelte";
13
16
  import AppShellToolbar from "./AppShellToolbar.svelte";
14
17
 
15
18
  export const Sidebar = Object.assign(AppShellSidebar, {
@@ -26,6 +29,11 @@ export const Body = Object.assign(AppShellBody, {
26
29
  Toggle: AppShellBodyToggle,
27
30
  });
28
31
 
32
+ export const SurfaceLayer = Object.assign(AppShellSurfaceLayer, {
33
+ Header: AppShellSurfaceLayerHeader,
34
+ Body: AppShellSurfaceLayerBody,
35
+ });
36
+
29
37
  export {
30
38
  AppShellBody,
31
39
  AppShellBodyContent,
@@ -39,6 +47,9 @@ export {
39
47
  AppShellSidebarFooter,
40
48
  AppShellSidebarHeader,
41
49
  AppShellSidebarToggle,
50
+ AppShellSurfaceLayer,
51
+ AppShellSurfaceLayerBody,
52
+ AppShellSurfaceLayerHeader,
42
53
  AppShellToolbar,
43
54
  AppShellBodyContent as BodyContent,
44
55
  AppShellBodySidebar as BodySidebar,
@@ -50,6 +61,8 @@ export {
50
61
  AppShellSidebarFooter as SidebarFooter,
51
62
  AppShellSidebarHeader as SidebarHeader,
52
63
  AppShellSidebarToggle as SidebarToggle,
64
+ AppShellSurfaceLayerBody as SurfaceLayerBody,
65
+ AppShellSurfaceLayerHeader as SurfaceLayerHeader,
53
66
  AppShellToolbar as Toolbar,
54
67
  };
55
68
 
@@ -59,6 +72,7 @@ export const AppShell = {
59
72
  Main: AppShellMain,
60
73
  Toolbar: AppShellToolbar,
61
74
  Body,
75
+ SurfaceLayer,
62
76
  } as const;
63
77
 
64
78
  export * from "./app-shell-body-context.svelte.js";
@@ -19,4 +19,6 @@
19
19
  color-mix(in srgb, var(--foreground) 10%, transparent),
20
20
  0 1px 2px -1px color-mix(in srgb, var(--foreground) 10%, transparent);
21
21
  --ui-shell-main-border: 1px solid var(--border);
22
+ --ui-shell-surface-layer-main-start-allowance: 0px;
23
+ --ui-shell-surface-layer-inline-end-inset: var(--ui-shell-main-block-inset);
22
24
  }
@@ -14,6 +14,9 @@ export const shellTokenNames = {
14
14
  mainRadius: "--ui-shell-main-radius",
15
15
  mainShadow: "--ui-shell-main-shadow",
16
16
  mainBorder: "--ui-shell-main-border",
17
+ surfaceLayerMainStartAllowance:
18
+ "--ui-shell-surface-layer-main-start-allowance",
19
+ surfaceLayerInlineEndInset: "--ui-shell-surface-layer-inline-end-inset",
17
20
  } as const;
18
21
 
19
22
  export type ShellTokenKey = keyof typeof shellTokenNames;
@@ -36,4 +39,6 @@ export const shellTokenDefaults: Record<ShellTokenKey, string> = {
36
39
  mainShadow:
37
40
  "0 1px 3px color-mix(in srgb, var(--foreground) 10%, transparent), 0 1px 2px -1px color-mix(in srgb, var(--foreground) 10%, transparent)",
38
41
  mainBorder: "1px solid var(--border)",
42
+ surfaceLayerMainStartAllowance: "0px",
43
+ surfaceLayerInlineEndInset: "var(--ui-shell-main-block-inset)",
39
44
  };
@@ -22,8 +22,9 @@
22
22
  event: MouseEvent,
23
23
  ) {
24
24
  if (entry.disabled) return;
25
+ const selectedMenu = menu;
25
26
  await entry.callback?.(event);
26
- if (entry.closeOnSelect) menu.hide();
27
+ if (entry.closeOnSelect) selectedMenu.hide();
27
28
  }
28
29
  </script>
29
30
 
@@ -22,8 +22,9 @@
22
22
  event: MouseEvent,
23
23
  ) {
24
24
  if (entry.disabled) return;
25
+ const selectedMenu = menu;
25
26
  await entry.callback?.(event);
26
- if (entry.closeOnSelect) menu.hide();
27
+ if (entry.closeOnSelect) selectedMenu.hide();
27
28
  }
28
29
  </script>
29
30
 
@@ -30,10 +30,7 @@
30
30
  class="ui-workspace-problems__severity"
31
31
  name={severityIcon}
32
32
  />
33
- <WorkspaceIcon
34
- class="ui-workspace-problems__lightbulb"
35
- name="lightbulb"
36
- />
33
+ <WorkspaceIcon class="ui-workspace-problems__lightbulb" name="lightbulb" />
37
34
  </DropdownMenu.Trigger>
38
35
  <DropdownMenu.Portal>
39
36
  <DropdownMenu.Content
@@ -90,6 +90,12 @@
90
90
  settingsState.controller.selectSection(result.sectionId);
91
91
  settingsState.query = "";
92
92
  await tick();
93
+ if (result.fieldId) {
94
+ const section = settingsState.controller.sections.find(
95
+ (candidate) => candidate.id === result.sectionId,
96
+ );
97
+ await section?.revealSearchEntry?.(result.fieldId);
98
+ }
93
99
  await tick();
94
100
  requestAnimationFrame(() => {
95
101
  const element = viewport?.querySelector<HTMLElement>(
@@ -2,6 +2,12 @@
2
2
  import WorkspaceIcon from "../icon/WorkspaceIcon.svelte";
3
3
  import { getAppSettingsContext } from "./app-settings-context.svelte.js";
4
4
 
5
+ let {
6
+ onselect,
7
+ }: {
8
+ onselect?: (sectionId: string) => void;
9
+ } = $props();
10
+
5
11
  const state = getAppSettingsContext();
6
12
  let groups = $derived.by(() => {
7
13
  const configured = new Map(
@@ -47,6 +53,7 @@
47
53
  onclick={() => {
48
54
  state.controller.selectSection(section.id);
49
55
  state.query = "";
56
+ onselect?.(section.id);
50
57
  }}
51
58
  >
52
59
  <WorkspaceIcon name={section.icon ?? "settings"} />
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from "svelte";
3
+ import { Button } from "@lapismd/design-core/shadcn/button";
4
+ import WorkspaceIcon from "../icon/WorkspaceIcon.svelte";
5
+ import { shortenCopyableValue } from "./copyable-value.js";
6
+
7
+ let {
8
+ value,
9
+ label = "value",
10
+ emptyLabel = "None",
11
+ leadingCharacters = 8,
12
+ trailingCharacters = 4,
13
+ class: className,
14
+ testId,
15
+ }: {
16
+ value?: string | null;
17
+ label?: string;
18
+ emptyLabel?: string;
19
+ leadingCharacters?: number;
20
+ trailingCharacters?: number;
21
+ class?: string;
22
+ testId?: string;
23
+ } = $props();
24
+
25
+ let copied = $state(false);
26
+ let failed = $state(false);
27
+ let resetTimer: ReturnType<typeof setTimeout> | undefined;
28
+ let displayValue = $derived(
29
+ value
30
+ ? shortenCopyableValue(value, leadingCharacters, trailingCharacters)
31
+ : emptyLabel,
32
+ );
33
+
34
+ onDestroy(() => {
35
+ if (resetTimer) clearTimeout(resetTimer);
36
+ });
37
+
38
+ async function copy(): Promise<void> {
39
+ if (!value) return;
40
+ copied = false;
41
+ failed = false;
42
+ try {
43
+ const clipboard = globalThis.navigator?.clipboard;
44
+ if (!clipboard?.writeText) throw new Error("Clipboard is unavailable");
45
+ await clipboard.writeText(value);
46
+ copied = true;
47
+ } catch {
48
+ failed = true;
49
+ }
50
+ if (resetTimer) clearTimeout(resetTimer);
51
+ resetTimer = setTimeout(() => {
52
+ copied = false;
53
+ failed = false;
54
+ }, 1_500);
55
+ }
56
+ </script>
57
+
58
+ {#if value}
59
+ <Button
60
+ class={["ui-workspace-copyable-value", className].filter(Boolean).join(" ")}
61
+ variant="ghost"
62
+ size="xs"
63
+ aria-label={`Copy full ${label}: ${value}`}
64
+ title={`Copy full ${label}`}
65
+ data-testid={testId}
66
+ data-copied={copied || undefined}
67
+ data-copy-failed={failed || undefined}
68
+ onclick={() => void copy()}
69
+ >
70
+ <code>{displayValue}</code>
71
+ <WorkspaceIcon
72
+ name={copied ? "check" : failed ? "triangle-alert" : "copy"}
73
+ />
74
+ </Button>
75
+ {:else}
76
+ <span
77
+ class={["ui-workspace-copyable-value__empty", className]
78
+ .filter(Boolean)
79
+ .join(" ")}>{displayValue}</span
80
+ >
81
+ {/if}
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import AppSettingsRoot from "./AppSettingsRoot.svelte";
3
+ import AppSettingsSection from "./AppSettingsSection.svelte";
4
+ import type { WorkspaceSettingsController } from "./settings-controller.svelte.js";
5
+ import type { WorkspaceSettingsSection } from "./types.js";
6
+
7
+ let {
8
+ controller,
9
+ section,
10
+ class: className = "",
11
+ }: {
12
+ controller: WorkspaceSettingsController;
13
+ section: WorkspaceSettingsSection;
14
+ class?: string;
15
+ } = $props();
16
+ </script>
17
+
18
+ <AppSettingsRoot {controller} class={className}>
19
+ <AppSettingsSection {section} />
20
+ </AppSettingsRoot>