nexus-shell 0.2.2 → 0.2.4

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
@@ -92,7 +92,7 @@ const [active, setActive] = useState<string | null>('files');
92
92
  />
93
93
  {active && (
94
94
  <SidebarPane title="Explorer" onClose={() => setActive(null)}>
95
- <TreeWidget data={files} onToggle={toggle} onActivate={open} />
95
+ <TreeWidget data={nodes} onToggle={toggle} onActivate={open} />
96
96
  </SidebarPane>
97
97
  )}
98
98
  <main className="flex-1">{children}</main>
@@ -128,12 +128,13 @@ shell's own frame and keep every registration working.
128
128
  | `StatusBar` | Footer with left / center / right item groups |
129
129
  | `TerminalPane` | Bottom-docked terminal log and input |
130
130
  | `ThemeSwitcher` | Segmented theme control |
131
- | `TreeWidget` | Virtualised file tree with drag-to-move and a data-driven menu |
131
+ | `TreeWidget` | Virtualised tree for any hierarchy, with drag-to-move and a data-driven menu |
132
132
  | `UserProfile` | Avatar and identity widget |
133
133
 
134
134
  ## Theming
135
135
 
136
- Three themes ship in the stylesheet: `theme-light`, `theme-dark`, `theme-gt`.
136
+ Four themes ship in the stylesheet: `theme-light`, `theme-dark`, `theme-gt`
137
+ (Georgia Tech) and `theme-tamu` (Texas A&M).
137
138
  Colors are CSS custom properties scoped to those classes, so switching themes is
138
139
  a class on an ancestor — no React context, no provider.
139
140
 
@@ -6,13 +6,18 @@ export interface ISettingsThemeOption<T extends string = string> {
6
6
  label: string;
7
7
  icon: LucideIcon;
8
8
  }
9
+ /**
10
+ * The themes bundled in the library stylesheet, with their full labels.
11
+ * Derived from the theme registry, so a new bundled theme appears here without
12
+ * a code change.
13
+ */
9
14
  export declare const DEFAULT_SETTINGS_THEMES: ISettingsThemeOption[];
10
15
  export interface SettingsPanelProps<T extends string = string> {
11
16
  /** Currently selected theme id. */
12
17
  theme: T;
13
18
  /** Called with the newly selected theme id. */
14
19
  onThemeChange: (theme: T) => void;
15
- /** Theme choices to offer. Defaults to the three bundled themes. */
20
+ /** Theme choices to offer. Defaults to every bundled theme. */
16
21
  themes?: ISettingsThemeOption<T>[];
17
22
  /** Extra classes merged onto the root element. */
18
23
  className?: string;
@@ -5,6 +5,11 @@ export interface IThemeOption<T extends string = string> {
5
5
  /** Short label shown on the segment. Kept terse — segments are compact. */
6
6
  label: string;
7
7
  }
8
+ /**
9
+ * The themes bundled in the library stylesheet, using their abbreviated labels
10
+ * because the segments are narrow. Derived from the theme registry, so a new
11
+ * bundled theme appears here without a code change.
12
+ */
8
13
  export declare const DEFAULT_THEME_OPTIONS: IThemeOption[];
9
14
  export interface ThemeSwitcherProps<T extends string = string> {
10
15
  /** Currently selected theme id. */
@@ -12,8 +17,8 @@ export interface ThemeSwitcherProps<T extends string = string> {
12
17
  /** Called with the newly selected theme id. */
13
18
  onChange: (theme: T) => void;
14
19
  /**
15
- * Selectable themes. Defaults to Light / Dark / Georgia Tech, matching the
16
- * themes shipped in the library's stylesheet.
20
+ * Selectable themes. Defaults to every theme bundled in the library's
21
+ * stylesheet. Supply your own to offer a different set.
17
22
  */
18
23
  options?: IThemeOption<T>[];
19
24
  /** Extra classes merged onto the root element. */
@@ -1,31 +1,60 @@
1
1
  import { default as React } from 'react';
2
2
 
3
- /** A node in the {@link TreeWidget} hierarchy. */
3
+ /**
4
+ * A node in the {@link TreeWidget} hierarchy.
5
+ *
6
+ * The model is deliberately domain-neutral: the component knows about branches
7
+ * and leaves, and nothing else. A file explorer, an org chart, a scene graph and
8
+ * a category picker are all the same shape here — what differs is your `kind`
9
+ * and your `icon`.
10
+ */
4
11
  export interface ITreeNode {
5
12
  /** Stable identifier, unique across the whole tree. */
6
13
  id: string;
7
14
  /** Text shown for the node. */
8
15
  label: string;
9
- /** `folder` nodes expand on click; `file` nodes do not. */
10
- type: 'file' | 'folder';
16
+ /**
17
+ * Whether this node can contain children — a folder, a department, a group.
18
+ * Branches expand on click and accept drops; leaves do neither.
19
+ *
20
+ * Defaults to `false`. Set it explicitly for a branch whose children haven't
21
+ * been loaded yet, so it still renders as expandable.
22
+ */
23
+ isBranch?: boolean;
24
+ /**
25
+ * Your own classification for this node, e.g. `"folder"`, `"department"`,
26
+ * `"question"`. The component never interprets it — it exists so context-menu
27
+ * actions can target node types through {@link ITreeAction.showFor}.
28
+ */
29
+ kind?: string;
11
30
  /** Child nodes. Only rendered while `isOpen` is `true`. */
12
31
  children?: ITreeNode[];
13
- /** Whether a folder is expanded. Controlled by you — see `onToggle`. */
32
+ /** Whether a branch is expanded. Controlled by you — see `onToggle`. */
14
33
  isOpen?: boolean;
34
+ /**
35
+ * Icon rendered before the label. Any node works — a `lucide-react` icon, an
36
+ * avatar, a coloured dot. Omit it for a label-only row.
37
+ */
38
+ icon?: React.ReactNode;
39
+ /** Extra classes merged onto this row. */
40
+ className?: string;
15
41
  }
16
- /**
17
- * Where a context-menu action applies.
18
- *
19
- * `nodeId` is `null` when the menu was opened on empty background, which is how
20
- * you distinguish "new file here" from "new file at the root".
21
- */
42
+ /** Where a context-menu action applies. */
22
43
  export interface ITreeContext {
23
44
  /** The right-clicked node's id, or `null` for the background. */
24
45
  nodeId: string | null;
25
46
  /** The right-clicked node, or `null` for the background. */
26
47
  node: ITreeNode | null;
27
48
  }
28
- /** A custom entry in the tree's right-click menu. */
49
+ /**
50
+ * Where a context-menu entry is offered.
51
+ *
52
+ * `'branch'` and `'leaf'` match structurally; `'background'` is a right-click on
53
+ * empty space. Any other string is matched against the node's `kind`, so you can
54
+ * scope an action to your own node types.
55
+ */
56
+ export type TreeActionScope = 'branch' | 'leaf' | 'background' | (string & {});
57
+ /** An entry in the tree's right-click menu. */
29
58
  export interface ITreeAction {
30
59
  /** Stable identifier. Used as the React key. */
31
60
  id: string;
@@ -38,39 +67,38 @@ export interface ITreeAction {
38
67
  /** Draw a separator above this entry. */
39
68
  divider?: boolean;
40
69
  /**
41
- * Restrict where the entry appears. Defaults to everywhere. Use this for
42
- * actions that only make sense on a folder, or only on the background.
70
+ * Restrict where the entry appears. Defaults to everywhere.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * showFor: ['branch', 'background'] // structural
75
+ * showFor: ['department'] // matches node.kind
76
+ * ```
43
77
  */
44
- showFor?: ('file' | 'folder' | 'background')[];
78
+ showFor?: TreeActionScope[];
45
79
  }
46
80
  export interface TreeWidgetProps {
47
81
  /** Root nodes. Expansion state lives on the nodes themselves via `isOpen`. */
48
82
  data: ITreeNode[];
49
83
  /**
50
- * Called when a folder is clicked. Flip that node's `isOpen` in your own
84
+ * Called when a branch is clicked. Flip that node's `isOpen` in your own
51
85
  * state — the tree renders expansion but does not own it.
52
86
  */
53
87
  onToggle?: (node: ITreeNode) => void;
54
- /** Called when a node is double-clicked. Typically opens the item. */
88
+ /** Called when a node is double-clicked or Enter is pressed on a leaf. */
55
89
  onActivate?: (node: ITreeNode) => void;
56
- /** Called after a node is dragged onto a folder. */
57
- onMoveNode?: (draggedId: string, targetFolderId: string) => void;
58
90
  /**
59
- * Entries for the right-click menu. Defaults to New File, New Folder, Rename
60
- * and Delete, which map to the `onNew*` / `onRename` / `onDelete` props.
91
+ * Called after a node is dragged onto a branch. Only branches accept drops.
92
+ * Reparenting is yours to perform — the tree does not mutate `data`.
93
+ */
94
+ onMoveNode?: (draggedId: string, targetBranchId: string) => void;
95
+ /**
96
+ * Entries for the right-click menu. Empty by default: the component ships no
97
+ * actions of its own, because "New File" means nothing to an org chart.
61
98
  *
62
- * Pass your own array to replace that set entirely — this is the extension
63
- * point for app-specific commands, so the tree itself stays generic.
99
+ * With no entries, no menu appears.
64
100
  */
65
101
  actions?: ITreeAction[];
66
- /** Handler for the built-in "New File" action. */
67
- onNewFile?: (parentId: string | null) => void;
68
- /** Handler for the built-in "New Folder" action. */
69
- onNewFolder?: (parentId: string | null) => void;
70
- /** Handler for the built-in "Rename" action. */
71
- onRename?: (nodeId: string) => void;
72
- /** Handler for the built-in "Delete" action. */
73
- onDelete?: (nodeId: string) => void;
74
102
  /** Indentation added per depth level, in px. Defaults to `12`. */
75
103
  indent?: number;
76
104
  /**
@@ -79,32 +107,49 @@ export interface TreeWidgetProps {
79
107
  * search, or a test environment that can't measure layout. Defaults to `true`.
80
108
  */
81
109
  virtualized?: boolean;
110
+ /** Accessible label for the tree. Defaults to `"Tree"`. */
111
+ 'aria-label'?: string;
82
112
  /** Extra classes merged onto the root element. */
83
113
  className?: string;
84
114
  }
85
115
  /**
86
- * A virtualised file-explorer tree with drag-to-move and a right-click menu.
116
+ * A virtualised, domain-neutral tree with drag-to-move and a data-driven
117
+ * right-click menu.
87
118
  *
88
- * Rows are virtualised via `react-virtuoso`, so very large trees stay
89
- * responsive. The component is controlled: expansion lives on your nodes as
90
- * `isOpen` and changes are reported through `onToggle`, so the tree never holds
91
- * a second copy of your data.
119
+ * It renders any hierarchy — files, an org chart, a scene graph, nested
120
+ * categories, an argument map. The component's only structural concept is
121
+ * `isBranch`: branches expand and accept drops, leaves do neither. Everything
122
+ * else — what a node *is*, what icon it carries, what actions it offers — is
123
+ * yours to supply.
92
124
  *
93
- * The context menu is data-driven. The defaults cover the usual file
94
- * operations; pass `actions` to replace them with your own commands rather than
95
- * adding one-off props.
125
+ * Rows are virtualised via `react-virtuoso`, so large trees stay responsive.
126
+ *
127
+ * Controlled: expansion lives on your nodes as `isOpen` and changes are
128
+ * reported through `onToggle`, so the tree never holds a second copy of your
129
+ * data.
96
130
  *
97
131
  * @example
98
132
  * ```tsx
133
+ * // An org chart — no files in sight.
99
134
  * <TreeWidget
100
- * data={nodes}
101
- * onToggle={(n) => setNodes(toggle(nodes, n.id))}
102
- * onActivate={(n) => open(n.id)}
135
+ * data={[
136
+ * {
137
+ * id: 'eng',
138
+ * label: 'Engineering',
139
+ * isBranch: true,
140
+ * kind: 'department',
141
+ * isOpen: true,
142
+ * icon: <Building2 size={14} />,
143
+ * children: [
144
+ * { id: 'ada', label: 'Ada Lovelace', kind: 'person', icon: <User size={14} /> },
145
+ * ],
146
+ * },
147
+ * ]}
148
+ * onToggle={(node) => setNodes(toggle(nodes, node.id))}
103
149
  * actions={[
104
- * { id: 'new-map', label: 'New Map', icon: <Map size={14} />,
105
- * showFor: ['folder', 'background'], onSelect: (ctx) => createMap(ctx.nodeId) },
150
+ * { id: 'hire', label: 'Add report', showFor: ['department'], onSelect: addReport },
106
151
  * ]}
107
152
  * />
108
153
  * ```
109
154
  */
110
- export declare const TreeWidget: ({ data, onToggle, onActivate, onMoveNode, actions, onNewFile, onNewFolder, onRename, onDelete, indent, virtualized, className, }: TreeWidgetProps) => import("react/jsx-runtime").JSX.Element;
155
+ export declare const TreeWidget: ({ data, onToggle, onActivate, onMoveNode, actions, indent, virtualized, "aria-label": ariaLabel, className, }: TreeWidgetProps) => import("react/jsx-runtime").JSX.Element;
@@ -31,7 +31,7 @@ export interface InitializeShellOptions {
31
31
  defaultMenus?: boolean;
32
32
  }
33
33
  /** The command ids `initializeShell` registers unless told not to. */
34
- export declare const DEFAULT_COMMAND_IDS: readonly ["view.toggleTerminal", "view.toggleChat", "view.toggleSidebar", "theme.light", "theme.dark", "theme.gt"];
34
+ export declare const DEFAULT_COMMAND_IDS: readonly ["view.toggleTerminal", "view.toggleChat", "view.toggleSidebar", ...string[]];
35
35
  /**
36
36
  * Boot the shell: register commands, menus, panels and status bar items into
37
37
  * the shell registries and stores.
@@ -1,4 +1,13 @@
1
- export type ThemeType = 'light' | 'dark' | 'gt';
1
+ import { BundledThemeId } from '../../lib/themes';
2
+
3
+ /**
4
+ * A theme id.
5
+ *
6
+ * The bundled ids are suggested by autocomplete, but any string is accepted:
7
+ * define a `.theme-yourname` block with the same custom properties and it works
8
+ * exactly like the built-ins.
9
+ */
10
+ export type ThemeType = BundledThemeId | (string & {});
2
11
  interface ThemeState {
3
12
  theme: ThemeType;
4
13
  setTheme: (theme: ThemeType) => void;
package/dist/index.d.ts CHANGED
@@ -57,3 +57,4 @@ export { ThemeSwitcher, type ThemeSwitcherProps, type IThemeOption, DEFAULT_THEM
57
57
  export { TreeWidget, type TreeWidgetProps, type ITreeNode, type ITreeAction, type ITreeContext } from './components/widgets/TreeWidget';
58
58
  export { UserProfile, type UserProfileProps, type IUserProfile, type IUserProfileAction } from './components/widgets/UserProfile';
59
59
  export { cn } from './lib/cn';
60
+ export { BUNDLED_THEMES, BUNDLED_THEME_CLASSES, themeClass, type IBundledTheme, type BundledThemeId, } from './lib/themes';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The themes bundled in the library's stylesheet.
3
+ *
4
+ * This is the single source of truth. `ThemeService` derives its union type and
5
+ * its class-swapping from it, `ThemeSwitcher` and `SettingsPanel` derive their
6
+ * default options, `initializeShell` derives its theme commands and View menu,
7
+ * and Storybook derives its toolbar. Adding a theme is a block in `index.css`
8
+ * plus one entry here.
9
+ *
10
+ * It lives in `lib/` rather than `core/services/` because the presentational
11
+ * components need it, and they are forbidden from importing stores.
12
+ *
13
+ * Themes are not a closed set — see {@link ThemeSwitcher}'s `options` prop and
14
+ * {@link SettingsPanel}'s `themes` prop for supplying your own.
15
+ */
16
+ export interface IBundledTheme {
17
+ /** Matches the `.theme-<id>` class in the stylesheet. */
18
+ id: string;
19
+ /** Full name, for menus and the settings panel. */
20
+ label: string;
21
+ /** Abbreviated name, for the compact segmented switcher. */
22
+ shortLabel: string;
23
+ /** One line on what the theme is, used in documentation. */
24
+ description: string;
25
+ }
26
+ export declare const BUNDLED_THEMES: readonly [{
27
+ readonly id: "light";
28
+ readonly label: "Light";
29
+ readonly shortLabel: "Light";
30
+ readonly description: "Neutral light theme. The default.";
31
+ }, {
32
+ readonly id: "dark";
33
+ readonly label: "Dark";
34
+ readonly shortLabel: "Dark";
35
+ readonly description: "Low-glare dark theme on a soft grey base.";
36
+ }, {
37
+ readonly id: "gt";
38
+ readonly label: "Georgia Tech";
39
+ readonly shortLabel: "GT";
40
+ readonly description: "Buzz Gold (#EAAA00) on Tech Tower White.";
41
+ }, {
42
+ readonly id: "tamu";
43
+ readonly label: "Texas A&M";
44
+ readonly shortLabel: "TAMU";
45
+ readonly description: "Aggie Maroon (#500000) on white.";
46
+ }];
47
+ /** Ids of the themes that ship in the stylesheet. */
48
+ export type BundledThemeId = (typeof BUNDLED_THEMES)[number]['id'];
49
+ /** Every bundled theme's class name, e.g. `["theme-light", …]`. */
50
+ export declare const BUNDLED_THEME_CLASSES: string[];
51
+ /**
52
+ * The class name for a theme id.
53
+ *
54
+ * Accepts any string, not just a bundled id, so an app that defines its own
55
+ * `.theme-*` block can use it too.
56
+ */
57
+ export declare const themeClass: (id: string) => string;