@dsh-stack/settings-panel 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,75 @@
1
- # Temporary Holding Version
1
+ # @dsh-stack/settings-panel
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The canonical shape of a DSH Stack settings section.
4
+
5
+ Every Stack settings section renders the same furniture: a titled header with a
6
+ description, a vertical grid of options, selectable rows, and a close action.
7
+ Before this package each section hand-rolled that shape with inline styles —
8
+ `sidebar-settings`, `skin-settings` and `profile-ui` each carried their own
9
+ copy. The copies drifted (different border tokens, different type scales), and
10
+ the duplication was suppressed with `jscpd:ignore` markers rather than removed,
11
+ which `.agents/AGENTS.md` forbids: there is exactly one implementation owner for
12
+ every feature.
13
+
14
+ This package is that owner. Sections compose it and contribute only their own
15
+ content.
16
+
17
+ ## Exports
18
+
19
+ | Export | Renders |
20
+ | --- | --- |
21
+ | `SettingsSection` | section frame: accessible label, header with title and description, and an option grid |
22
+ | `SettingsOptionRow` | one selectable option row with a checkmark when selected — skins, profiles |
23
+ | `SettingsToggleRow` | one labelled option row with a description and a checkbox — sidebar preferences |
24
+ | `SettingsCloseButton` | the section's close action |
25
+
26
+ ## Slot seats
27
+
28
+ Two settings slots are declared at runtime by hand-authored JavaScript that
29
+ carries no TypeScript face, so their `SlotMap` types live here — in the package
30
+ every Stack settings registrant already depends on.
31
+
32
+ | Export | Seat | Filled by |
33
+ | --- | --- | --- |
34
+ | `SETTINGS_SECTION_ICON_SLOT` | `settings.section.icon` — one nav glyph per settings section, keyed by section id | whichever plugin owns the matching `settings.section` entry |
35
+ | `SETTINGS_APPEARANCE_TAB_SLOT` | `settings.appearance.tab` — one page inside the Appearance section | the packages owning the theme studio, skins, icons and themes |
36
+
37
+ `settings.appearance.tab` is what makes Appearance a single settings
38
+ destination without its owner importing the three feature packages whose pages
39
+ it shows; see `.agents/notes/decisions/settings-section-composition.md`.
40
+
41
+ ## Usage
42
+
43
+ ```tsx
44
+ import {
45
+ SettingsSection,
46
+ SettingsOptionRow,
47
+ SettingsCloseButton,
48
+ } from "@dsh-stack/settings-panel";
49
+
50
+ <SettingsSection
51
+ label="Skins"
52
+ title="Skins"
53
+ description="Change the DSH Stack visual identity."
54
+ onClose={close}
55
+ >
56
+ {skins.map((skin) => (
57
+ <SettingsOptionRow
58
+ key={skin.id}
59
+ label={skin.label}
60
+ selected={skin.id === active}
61
+ onSelect={() => setActive(skin.id)}
62
+ />
63
+ ))}
64
+ </SettingsSection>
65
+ ```
66
+
67
+ `SettingsSection` renders `SettingsCloseButton` itself when given `onClose`.
68
+
69
+ ## Styling
70
+
71
+ Presentation lives here as inline style objects, matching how the surrounding
72
+ Stack settings surfaces are written today; there is no CSS-module build step in
73
+ this layer. Colours are expressed with `color-mix(in srgb, currentColor …)` so a
74
+ section inherits the host shell's palette and stays correct under every skin
75
+ without importing theme tokens.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Owner share of one Appearance tab. The section owns tab selection and the
3
+ * tab strip; a tab draws its own body and receives the same `close` affordance
4
+ * a top-level section gets, so a tab that used to be its own section keeps
5
+ * whatever close action it already rendered.
6
+ */
7
+ export interface SettingsAppearanceTabOwnerProps {
8
+ /** Close the settings panel (the shell owns the open state). */
9
+ close: () => void;
10
+ }
11
+ declare module "@deepseek-ai/dsh-client-ui-slots" {
12
+ interface SlotMap {
13
+ /**
14
+ * One page inside the Appearance settings section. Options carry the tab
15
+ * identity: `id` (tab key, drives `only` filtering), `order` (tab
16
+ * position) and `label` (registrant-owned tab text). Declared at runtime
17
+ * by the Appearance section; registrants never depend on one another.
18
+ */
19
+ "settings.appearance.tab": {
20
+ kind: "list";
21
+ scope: "root";
22
+ owner: SettingsAppearanceTabOwnerProps;
23
+ };
24
+ }
25
+ }
26
+ /**
27
+ * The seat's slot key, re-exported so registrants name it once instead of
28
+ * repeating the string literal at every `inject`/`register` pair.
29
+ */
30
+ export declare const SETTINGS_APPEARANCE_TAB_SLOT: "settings.appearance.tab";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The seat's slot key, re-exported so registrants name it once instead of
3
+ * repeating the string literal at every `inject`/`register` pair.
4
+ */
5
+ export const SETTINGS_APPEARANCE_TAB_SLOT = "settings.appearance.tab";
package/lib/index.d.ts ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Canonical settings-section furniture shared by every Stack settings surface.
3
+ *
4
+ * `sidebar-settings`, `skin-settings` and `profile-ui` each used to hand-roll
5
+ * the same section frame, option grid and selectable row with inline styles.
6
+ * The copies drifted apart and the duplication was suppressed with
7
+ * `jscpd:ignore` markers instead of removed. This module is the single owner of
8
+ * that shape; sections contribute only their own content.
9
+ *
10
+ * @module @dsh-stack/settings-panel
11
+ */
12
+ import type { ReactNode } from "react";
13
+ export { SETTINGS_SECTION_ICON_SLOT } from "./section-icon-slot.js";
14
+ export type { SettingsSectionIconOwnerProps } from "./section-icon-slot.js";
15
+ export { SETTINGS_APPEARANCE_TAB_SLOT } from "./appearance-tab-slot.js";
16
+ export type { SettingsAppearanceTabOwnerProps } from "./appearance-tab-slot.js";
17
+ export interface SettingsSectionProps {
18
+ /** Accessible name for the section landmark. */
19
+ readonly label: string;
20
+ /** Heading shown at the top of the section. */
21
+ readonly title: string;
22
+ /** Supporting copy under the heading. */
23
+ readonly description?: string;
24
+ /** Option rows; rendered inside the shared option grid. */
25
+ readonly children: ReactNode;
26
+ /** Close handler; when supplied the section renders its own close action. */
27
+ readonly onClose?: () => void;
28
+ /** Label for the close action. */
29
+ readonly closeLabel?: string;
30
+ }
31
+ /**
32
+ * The frame every Stack settings section renders inside.
33
+ * @param props - section content and close behaviour.
34
+ * @returns the section landmark, header, option grid and close action.
35
+ */
36
+ export declare function SettingsSection({ label, title, description, children, onClose, closeLabel, }: SettingsSectionProps): import("react").JSX.Element;
37
+ export interface SettingsOptionRowProps {
38
+ /** Text shown on the row. */
39
+ readonly label: string;
40
+ /** Whether this row is the current selection. */
41
+ readonly selected: boolean;
42
+ /** Invoked when the row is chosen. */
43
+ readonly onSelect: () => void;
44
+ /**
45
+ * Mark rendered on the selected row. Sections that own an icon set pass their
46
+ * own glyph; the default keeps the row usable without one.
47
+ */
48
+ readonly selectedMark?: ReactNode;
49
+ }
50
+ /**
51
+ * One selectable option in a settings section — a skin, a profile, a theme.
52
+ * @param props - row label, selection state and select handler.
53
+ * @returns a pressable row that marks itself when selected.
54
+ */
55
+ export declare function SettingsOptionRow({ label, selected, onSelect, selectedMark, }: SettingsOptionRowProps): import("react").JSX.Element;
56
+ export interface SettingsToggleRowProps {
57
+ /** Element id linking the row's label to its checkbox. */
58
+ readonly id: string;
59
+ /** Text shown on the row. */
60
+ readonly label: string;
61
+ /** Supporting copy under the label. */
62
+ readonly description: string;
63
+ /** Current value. */
64
+ readonly checked: boolean;
65
+ /** Invoked with the new value when the row is toggled. */
66
+ readonly onChange: (value: boolean) => void;
67
+ }
68
+ /**
69
+ * One boolean preference in a settings section.
70
+ * @param props - row identity, copy, value and change handler.
71
+ * @returns a labelled row wrapping a checkbox.
72
+ */
73
+ export declare function SettingsToggleRow({ id, label, description, checked, onChange, }: SettingsToggleRowProps): import("react").JSX.Element;
74
+ export interface SettingsCloseButtonProps {
75
+ /** Button text. */
76
+ readonly label: string;
77
+ /** Invoked when the button is pressed. */
78
+ readonly onClose: () => void;
79
+ }
80
+ /**
81
+ * The close action a settings section renders below its options.
82
+ * @param props - button label and close handler.
83
+ * @returns the close button.
84
+ */
85
+ export declare function SettingsCloseButton({ label, onClose }: SettingsCloseButtonProps): import("react").JSX.Element;
package/lib/index.js ADDED
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export { SETTINGS_SECTION_ICON_SLOT } from "./section-icon-slot.js";
3
+ export { SETTINGS_APPEARANCE_TAB_SLOT } from "./appearance-tab-slot.js";
4
+ /** Vertical rhythm between a section's option rows, in pixels. */
5
+ const OPTION_GAP = 8;
6
+ /** Height of a single option row, in pixels. */
7
+ const ROW_MIN_HEIGHT = 46;
8
+ /** Corner radius shared by every option row, in pixels. */
9
+ const ROW_RADIUS = 9;
10
+ /**
11
+ * Hairline that reads correctly on any skin: it is derived from the inherited
12
+ * text colour rather than a theme token, so a section stays legible wherever
13
+ * the host shell mounts it.
14
+ */
15
+ const ROW_BORDER = "1px solid color-mix(in srgb, currentColor 14%, transparent)";
16
+ /** Fill applied to the row representing the current selection. */
17
+ const ROW_SELECTED_BACKGROUND = "color-mix(in srgb, currentColor 7%, transparent)";
18
+ const sectionStyle = { maxWidth: 720, display: "grid", gap: 18 };
19
+ const titleStyle = { margin: 0, fontSize: 17, fontWeight: 650 };
20
+ const descriptionStyle = {
21
+ margin: "6px 0 0",
22
+ fontSize: 13,
23
+ lineHeight: 1.5,
24
+ opacity: 0.68,
25
+ };
26
+ const optionGridStyle = { display: "grid", gap: OPTION_GAP };
27
+ const rowBaseStyle = {
28
+ display: "flex",
29
+ alignItems: "center",
30
+ justifyContent: "space-between",
31
+ gap: 12,
32
+ minHeight: ROW_MIN_HEIGHT,
33
+ padding: "0 12px",
34
+ borderRadius: ROW_RADIUS,
35
+ border: ROW_BORDER,
36
+ color: "inherit",
37
+ font: "inherit",
38
+ textAlign: "left",
39
+ cursor: "pointer",
40
+ };
41
+ const rowLabelStackStyle = { display: "grid", gap: 2 };
42
+ const rowLabelStyle = { fontSize: 14 };
43
+ const rowDescriptionStyle = { fontSize: 12, lineHeight: 1.4, opacity: 0.68 };
44
+ const closeButtonStyle = { justifySelf: "start" };
45
+ /**
46
+ * The frame every Stack settings section renders inside.
47
+ * @param props - section content and close behaviour.
48
+ * @returns the section landmark, header, option grid and close action.
49
+ */
50
+ export function SettingsSection({ label, title, description, children, onClose, closeLabel = "Close", }) {
51
+ return (_jsxs("section", { "aria-label": label, style: sectionStyle, children: [_jsxs("header", { children: [_jsx("h2", { style: titleStyle, children: title }), description === undefined ? null : _jsx("p", { style: descriptionStyle, children: description })] }), _jsx("div", { style: optionGridStyle, children: children }), onClose === undefined ? null : _jsx(SettingsCloseButton, { label: closeLabel, onClose: onClose })] }));
52
+ }
53
+ /**
54
+ * One selectable option in a settings section — a skin, a profile, a theme.
55
+ * @param props - row label, selection state and select handler.
56
+ * @returns a pressable row that marks itself when selected.
57
+ */
58
+ export function SettingsOptionRow({ label, selected, onSelect, selectedMark, }) {
59
+ return (_jsxs("button", { type: "button", "aria-pressed": selected, onClick: onSelect, style: {
60
+ ...rowBaseStyle,
61
+ background: selected ? ROW_SELECTED_BACKGROUND : "transparent",
62
+ }, children: [_jsx("span", { style: rowLabelStyle, children: label }), selected ? (selectedMark ?? _jsx("span", { "aria-hidden": "true", children: "\u2713" })) : null] }));
63
+ }
64
+ /**
65
+ * One boolean preference in a settings section.
66
+ * @param props - row identity, copy, value and change handler.
67
+ * @returns a labelled row wrapping a checkbox.
68
+ */
69
+ export function SettingsToggleRow({ id, label, description, checked, onChange, }) {
70
+ return (_jsxs("label", { htmlFor: id, style: { ...rowBaseStyle, padding: "10px 12px" }, children: [_jsxs("span", { style: rowLabelStackStyle, children: [_jsx("span", { style: rowLabelStyle, children: label }), _jsx("span", { style: rowDescriptionStyle, children: description })] }), _jsx("input", { id: id, type: "checkbox", checked: checked, onChange: (event) => onChange(event.target.checked) })] }));
71
+ }
72
+ /**
73
+ * The close action a settings section renders below its options.
74
+ * @param props - button label and close handler.
75
+ * @returns the close button.
76
+ */
77
+ export function SettingsCloseButton({ label, onClose }) {
78
+ return (_jsx("button", { type: "button", onClick: onClose, style: closeButtonStyle, children: label }));
79
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Owner share of a section glyph. The shell renders the nav cell and passes
3
+ * nothing: the glyph draws itself at its own size, inheriting `currentColor`
4
+ * from the nav row so selection and hover states stay the shell's business.
5
+ */
6
+ export interface SettingsSectionIconOwnerProps {
7
+ /** Marker field: glyph owner props are intentionally empty. */
8
+ children?: never;
9
+ }
10
+ declare module "@deepseek-ai/dsh-client-ui-slots" {
11
+ interface SlotMap {
12
+ /**
13
+ * One nav glyph per settings section, keyed by the section's `id`. A
14
+ * section with no entry here falls back to the shell's own mark for that
15
+ * id, so the nav never renders a blank cell.
16
+ */
17
+ "settings.section.icon": {
18
+ kind: "list";
19
+ scope: "root";
20
+ owner: SettingsSectionIconOwnerProps;
21
+ };
22
+ }
23
+ }
24
+ /**
25
+ * The seat's slot key, re-exported so registrants name it once instead of
26
+ * repeating the string literal at every `inject`/`register` pair.
27
+ */
28
+ export declare const SETTINGS_SECTION_ICON_SLOT: "settings.section.icon";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The seat's slot key, re-exported so registrants name it once instead of
3
+ * repeating the string literal at every `inject`/`register` pair.
4
+ */
5
+ export const SETTINGS_SECTION_ICON_SLOT = "settings.section.icon";
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@dsh-stack/settings-panel",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./lib/index.d.ts",
10
+ "default": "./lib/index.js"
11
+ },
12
+ "./package.json": "./package.json"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
17
+ "verify": "node verify.mjs"
18
+ },
19
+ "peerDependencies": {
20
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
21
+ "react": "^18.2.0"
22
+ },
23
+ "devDependencies": {
24
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
25
+ "@types/react": "~18.3.1",
26
+ "react": "^18.3.1",
27
+ "typescript": "^6.0.3"
28
+ },
29
+ "stack": {
30
+ "kind": "plugin",
31
+ "id": "stack.ui.settings-panel"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "files": [
37
+ "lib",
38
+ "README.md"
39
+ ],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/marius-patrik/dsh-stack.git",
43
+ "directory": "plugins/settings-panel"
44
+ }
45
+ }