@columenlabs/lumen-ui 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +4 -2
- package/dist/add-column.d.ts +23 -0
- package/dist/add-column.js +62 -0
- package/dist/api-fetch.d.ts +9 -0
- package/dist/api-fetch.js +60 -0
- package/dist/app-switcher.d.ts +21 -0
- package/dist/app-switcher.js +12 -0
- package/dist/app-views.d.ts +18 -0
- package/dist/app-views.js +41 -0
- package/dist/assistant-context.d.ts +72 -0
- package/dist/assistant-context.js +30 -0
- package/dist/assistant-panel.d.ts +58 -0
- package/dist/assistant-panel.js +244 -0
- package/dist/assistant-screen.d.ts +124 -0
- package/dist/assistant-screen.js +252 -0
- package/dist/card-switch.d.ts +16 -0
- package/dist/card-switch.js +16 -0
- package/dist/color-dot.d.ts +14 -0
- package/dist/color-dot.js +19 -0
- package/dist/confirm.d.ts +18 -0
- package/dist/confirm.js +5 -0
- package/dist/connections.d.ts +45 -0
- package/dist/connections.js +275 -0
- package/dist/coverage-calendar-map.d.ts +27 -0
- package/dist/coverage-calendar-map.js +84 -0
- package/dist/coverage-cells.d.ts +55 -0
- package/dist/coverage-cells.js +85 -0
- package/dist/coverage-map.d.ts +112 -0
- package/dist/coverage-map.js +428 -0
- package/dist/details-table.d.ts +20 -0
- package/dist/details-table.js +13 -0
- package/dist/drawer-section.d.ts +27 -0
- package/dist/drawer-section.js +23 -0
- package/dist/floating-head.d.ts +2 -0
- package/dist/floating-head.js +57 -0
- package/dist/fold.d.ts +16 -0
- package/dist/fold.js +70 -0
- package/dist/form-issues.d.ts +32 -0
- package/dist/form-issues.js +60 -0
- package/dist/formula-input.d.ts +17 -0
- package/dist/formula-input.js +104 -0
- package/dist/frame-script.d.ts +7 -0
- package/dist/frame-script.js +103 -0
- package/dist/freshness.d.ts +24 -0
- package/dist/freshness.js +99 -0
- package/dist/group-button.d.ts +33 -0
- package/dist/group-button.js +67 -0
- package/dist/growth-chart.d.ts +77 -0
- package/dist/growth-chart.js +195 -0
- package/dist/hooks/use-keyboard-fit.d.ts +15 -0
- package/dist/hooks/use-keyboard-fit.js +29 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-mobile.js +15 -0
- package/dist/icon-button.d.ts +21 -0
- package/dist/icon-button.js +22 -0
- package/dist/icon-picker.d.ts +14 -0
- package/dist/icon-picker.js +36 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +90 -0
- package/dist/integrations.d.ts +57 -0
- package/dist/integrations.js +73 -0
- package/dist/joins.d.ts +128 -0
- package/dist/joins.js +767 -0
- package/dist/kit-config.d.ts +13 -0
- package/dist/kit-config.js +15 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/lumen-boundary.d.ts +7 -0
- package/dist/lumen-boundary.js +10 -0
- package/dist/lumen-code-editor-impl.d.ts +2 -0
- package/dist/lumen-code-editor-impl.js +16 -0
- package/dist/lumen-code-editor.d.ts +11 -0
- package/dist/lumen-code-editor.js +11 -0
- package/dist/lumen-code.d.ts +22 -0
- package/dist/lumen-code.js +75 -0
- package/dist/lumen-drawer.d.ts +30 -0
- package/dist/lumen-drawer.js +27 -0
- package/dist/lumen-echart.d.ts +58 -0
- package/dist/lumen-echart.js +129 -0
- package/dist/lumen-filter.d.ts +75 -0
- package/dist/lumen-filter.js +546 -0
- package/dist/lumen-finder.d.ts +62 -0
- package/dist/lumen-finder.js +275 -0
- package/dist/lumen-form.d.ts +34 -0
- package/dist/lumen-form.js +81 -0
- package/dist/lumen-geo-map.d.ts +9 -0
- package/dist/lumen-geo-map.js +135 -0
- package/dist/lumen-grid.d.ts +49 -0
- package/dist/lumen-grid.js +203 -0
- package/dist/lumen-layout.d.ts +150 -0
- package/dist/lumen-layout.js +661 -0
- package/dist/lumen-links.d.ts +96 -0
- package/dist/lumen-links.js +213 -0
- package/dist/lumen-page-state.d.ts +20 -0
- package/dist/lumen-page-state.js +15 -0
- package/dist/lumen-selector.d.ts +139 -0
- package/dist/lumen-selector.js +308 -0
- package/dist/lumen-source.d.ts +137 -0
- package/dist/lumen-source.js +327 -0
- package/dist/lumen-tab-drawer.d.ts +27 -0
- package/dist/lumen-tab-drawer.js +26 -0
- package/dist/lumen-tabs.d.ts +21 -0
- package/dist/lumen-tabs.js +6 -0
- package/dist/lumen-toast.d.ts +11 -0
- package/dist/lumen-toast.js +16 -0
- package/dist/lumen-trend.d.ts +18 -0
- package/dist/lumen-trend.js +157 -0
- package/dist/lumen-view-card.d.ts +35 -0
- package/dist/lumen-view-card.js +50 -0
- package/dist/lumen-view.d.ts +172 -0
- package/dist/lumen-view.js +645 -0
- package/dist/lumen-widget-charts.d.ts +57 -0
- package/dist/lumen-widget-charts.js +69 -0
- package/dist/lumen-widget.d.ts +720 -0
- package/dist/lumen-widget.js +609 -0
- package/dist/market/compare-chart.d.ts +34 -0
- package/dist/market/compare-chart.js +292 -0
- package/dist/market/indicator-picker.d.ts +29 -0
- package/dist/market/indicator-picker.js +125 -0
- package/dist/market/indicators.d.ts +63 -0
- package/dist/market/indicators.js +314 -0
- package/dist/market/live.d.ts +120 -0
- package/dist/market/live.js +444 -0
- package/dist/market/lumen-chart.d.ts +88 -0
- package/dist/market/lumen-chart.js +645 -0
- package/dist/market/market-widget.d.ts +45 -0
- package/dist/market/market-widget.js +346 -0
- package/dist/market/option-chain.d.ts +19 -0
- package/dist/market/option-chain.js +53 -0
- package/dist/market/price-chart.d.ts +128 -0
- package/dist/market/price-chart.js +619 -0
- package/dist/mcp-settings.d.ts +20 -0
- package/dist/mcp-settings.js +65 -0
- package/dist/nav.d.ts +77 -0
- package/dist/nav.js +73 -0
- package/dist/note-line.d.ts +13 -0
- package/dist/note-line.js +5 -0
- package/dist/notifications.d.ts +9 -0
- package/dist/notifications.js +166 -0
- package/dist/origin-legend.d.ts +14 -0
- package/dist/origin-legend.js +38 -0
- package/dist/overview.d.ts +84 -0
- package/dist/overview.js +105 -0
- package/dist/page-inputs.d.ts +14 -0
- package/dist/page-inputs.js +16 -0
- package/dist/page.d.ts +32 -0
- package/dist/page.js +45 -0
- package/dist/pager.d.ts +10 -0
- package/dist/pager.js +14 -0
- package/dist/prefs.d.ts +71 -0
- package/dist/prefs.js +126 -0
- package/dist/query-client.d.ts +7 -0
- package/dist/query-client.js +19 -0
- package/dist/refresh.d.ts +4 -0
- package/dist/refresh.js +19 -0
- package/dist/rename-field.d.ts +11 -0
- package/dist/rename-field.js +26 -0
- package/dist/reset-button.d.ts +9 -0
- package/dist/reset-button.js +10 -0
- package/dist/route-error.d.ts +42 -0
- package/dist/route-error.js +229 -0
- package/dist/schedules.d.ts +88 -0
- package/dist/schedules.js +221 -0
- package/dist/settings.d.ts +51 -0
- package/dist/settings.js +181 -0
- package/dist/shell.d.ts +99 -0
- package/dist/shell.js +137 -0
- package/dist/source-health.d.ts +12 -0
- package/dist/source-health.js +58 -0
- package/dist/sourced-card.d.ts +10 -0
- package/dist/sourced-card.js +69 -0
- package/dist/spotlight.d.ts +23 -0
- package/dist/spotlight.js +138 -0
- package/dist/status-health.d.ts +16 -0
- package/dist/status-health.js +41 -0
- package/dist/status.d.ts +9 -0
- package/dist/status.js +12 -0
- package/dist/summary-cards.d.ts +56 -0
- package/dist/summary-cards.js +270 -0
- package/dist/symbol-sheet-context.d.ts +4 -0
- package/dist/symbol-sheet-context.js +5 -0
- package/dist/symbol-sheet.d.ts +9 -0
- package/dist/symbol-sheet.js +24 -0
- package/dist/tasks.d.ts +29 -0
- package/dist/tasks.js +278 -0
- package/dist/theme.css +606 -0
- package/dist/tile.d.ts +37 -0
- package/dist/tile.js +23 -0
- package/dist/ui/alert-dialog.d.ts +18 -0
- package/dist/ui/alert-dialog.js +41 -0
- package/dist/ui/alert.d.ts +10 -0
- package/dist/ui/alert.js +27 -0
- package/dist/ui/avatar.d.ts +11 -0
- package/dist/ui/avatar.js +22 -0
- package/dist/ui/badge.d.ts +9 -0
- package/dist/ui/badge.js +27 -0
- package/dist/ui/breadcrumb.d.ts +11 -0
- package/dist/ui/breadcrumb.js +27 -0
- package/dist/ui/button-group.d.ts +11 -0
- package/dist/ui/button-group.js +27 -0
- package/dist/ui/button.d.ts +10 -0
- package/dist/ui/button.js +38 -0
- package/dist/ui/card.d.ts +11 -0
- package/dist/ui/card.js +24 -0
- package/dist/ui/chart.d.ts +44 -0
- package/dist/ui/chart.js +117 -0
- package/dist/ui/checkbox.d.ts +4 -0
- package/dist/ui/checkbox.js +8 -0
- package/dist/ui/collapsible.d.ts +5 -0
- package/dist/ui/collapsible.js +12 -0
- package/dist/ui/combobox.d.ts +24 -0
- package/dist/ui/combobox.js +60 -0
- package/dist/ui/command.d.ts +18 -0
- package/dist/ui/command.js +34 -0
- package/dist/ui/dialog.d.ts +23 -0
- package/dist/ui/dialog.js +50 -0
- package/dist/ui/dropdown-menu.d.ts +29 -0
- package/dist/ui/dropdown-menu.js +50 -0
- package/dist/ui/emoji-picker.d.ts +6 -0
- package/dist/ui/emoji-picker.js +28 -0
- package/dist/ui/empty.d.ts +11 -0
- package/dist/ui/empty.js +33 -0
- package/dist/ui/field.d.ts +24 -0
- package/dist/ui/field.js +65 -0
- package/dist/ui/hover-card.d.ts +6 -0
- package/dist/ui/hover-card.js +13 -0
- package/dist/ui/input-group.d.ts +16 -0
- package/dist/ui/input-group.js +56 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.js +6 -0
- package/dist/ui/item.d.ts +23 -0
- package/dist/ui/item.js +67 -0
- package/dist/ui/kbd.d.ts +3 -0
- package/dist/ui/kbd.js +9 -0
- package/dist/ui/label.d.ts +4 -0
- package/dist/ui/label.js +7 -0
- package/dist/ui/popover.d.ts +10 -0
- package/dist/ui/popover.js +25 -0
- package/dist/ui/progress.d.ts +4 -0
- package/dist/ui/progress.js +9 -0
- package/dist/ui/resizable.d.ts +7 -0
- package/dist/ui/resizable.js +13 -0
- package/dist/ui/scroll-area.d.ts +5 -0
- package/dist/ui/scroll-area.js +10 -0
- package/dist/ui/select.d.ts +15 -0
- package/dist/ui/select.js +36 -0
- package/dist/ui/separator.d.ts +4 -0
- package/dist/ui/separator.js +7 -0
- package/dist/ui/sheet.d.ts +14 -0
- package/dist/ui/sheet.js +54 -0
- package/dist/ui/sidebar.d.ts +69 -0
- package/dist/ui/sidebar.js +206 -0
- package/dist/ui/skeleton.d.ts +2 -0
- package/dist/ui/skeleton.js +6 -0
- package/dist/ui/slider.d.ts +4 -0
- package/dist/ui/slider.js +9 -0
- package/dist/ui/sonner.d.ts +3 -0
- package/dist/ui/sonner.js +24 -0
- package/dist/ui/spinner.d.ts +2 -0
- package/dist/ui/spinner.js +7 -0
- package/dist/ui/switch.d.ts +6 -0
- package/dist/ui/switch.js +7 -0
- package/dist/ui/table.d.ts +10 -0
- package/dist/ui/table.js +31 -0
- package/dist/ui/tabs.d.ts +7 -0
- package/dist/ui/tabs.js +22 -0
- package/dist/ui/textarea.d.ts +3 -0
- package/dist/ui/textarea.js +6 -0
- package/dist/ui/toggle-group.d.ts +10 -0
- package/dist/ui/toggle-group.js +22 -0
- package/dist/ui/toggle.d.ts +9 -0
- package/dist/ui/toggle.js +25 -0
- package/dist/ui/tooltip.d.ts +7 -0
- package/dist/ui/tooltip.js +16 -0
- package/dist/view-edit-cell.d.ts +49 -0
- package/dist/view-edit-cell.js +198 -0
- package/dist/view-editor.d.ts +52 -0
- package/dist/view-editor.js +1081 -0
- package/dist/view-formulas.d.ts +61 -0
- package/dist/view-formulas.js +247 -0
- package/dist/view-icons.d.ts +20 -0
- package/dist/view-icons.js +70 -0
- package/dist/view-model.d.ts +390 -0
- package/dist/view-model.js +316 -0
- package/dist/whats-new.d.ts +3 -0
- package/dist/whats-new.js +53 -0
- package/dist/widget-controls.d.ts +9 -0
- package/dist/widget-controls.js +26 -0
- package/dist/widget-data.d.ts +4 -0
- package/dist/widget-data.js +32 -0
- package/dist/widget-settings.d.ts +516 -0
- package/dist/widget-settings.js +290 -0
- package/dist/widget-thumbs.d.ts +3 -0
- package/dist/widget-thumbs.js +29 -0
- package/dist/workspace-keys.d.ts +12 -0
- package/dist/workspace-keys.js +13 -0
- package/dist/workspaces.d.ts +57 -0
- package/dist/workspaces.js +473 -0
- package/package.json +82 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Columen Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Lumen
|
|
2
|
+
Copyright (c) 2026 Columen Labs. Licensed under the MIT License (see LICENSE).
|
|
3
|
+
|
|
4
|
+
Lumen draws price and market charts with TradingView Lightweight Charts™
|
|
5
|
+
(https://github.com/tradingview/lightweight-charts), Copyright (c) 2025
|
|
6
|
+
TradingView, Inc., licensed under the Apache License 2.0. Lumen turns off the
|
|
7
|
+
chart's own logo, so per that library's NOTICE, an app that shows these charts
|
|
8
|
+
should link to https://www.tradingview.com/ somewhere a user can find it (an
|
|
9
|
+
About page or footer is enough).
|
|
10
|
+
|
|
11
|
+
The components under packages/lumen/src/ui are based on shadcn/ui
|
|
12
|
+
(https://ui.shadcn.com), MIT License, Copyright (c) 2023 shadcn.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @columenlabs/lumen-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lumen's components: the app frame (`LumenShell`), pages and views (`LumenLayout`, `LumenView`, `LumenTable`, `LumenWidget`, `LumenCard`), charts (`LumenChart`), `LumenDrawer`, `LumenSelector`, the shadcn parts under `ui/*` and one `theme.css`. Its types are in `@columenlabs/lumen-ui-types`.
|
|
4
|
+
|
|
5
|
+
See the [Lumen README](https://github.com/columenlabs/lumen-ui#readme) for install and use. MIT.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { YourColumn } from '@columenlabs/lumen-ui-types/frames';
|
|
2
|
+
import type { ViewFormula } from '@columenlabs/lumen-ui-types/views';
|
|
3
|
+
import type { ViewDomain } from './view-model.js';
|
|
4
|
+
/** A new formula column: label, key, expression, format and roll-up, checked before Add. */
|
|
5
|
+
export declare function AddFormulaDialog<T>({ open, onOpenChange, domain, formulas, onAdd, }: {
|
|
6
|
+
open: boolean;
|
|
7
|
+
onOpenChange: (open: boolean) => void;
|
|
8
|
+
/** The domain with its join fields (not its formulas), for the keys a formula can read. */
|
|
9
|
+
domain: ViewDomain<T>;
|
|
10
|
+
/** The view's formulas so far: their keys are taken, and readable. */
|
|
11
|
+
formulas: ViewFormula[];
|
|
12
|
+
onAdd: (formula: ViewFormula) => void;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
/** A Your data column: its name and whether it holds numbers or text. */
|
|
15
|
+
export declare function YourDataDialog({ open, onOpenChange, keyLabel, busy, error, onAdd, }: {
|
|
16
|
+
open: boolean;
|
|
17
|
+
onOpenChange: (open: boolean) => void;
|
|
18
|
+
/** What a row is keyed by: `symbol` or `account`, for the line under the form. */
|
|
19
|
+
keyLabel: string;
|
|
20
|
+
busy?: boolean;
|
|
21
|
+
error?: string | null;
|
|
22
|
+
onAdd: (column: YourColumn) => void;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { HashIcon, TypeIcon } from 'lucide-react';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useAssistantName } from './assistant-context.js';
|
|
5
|
+
import { SaveButton } from './form-issues.js';
|
|
6
|
+
import { LumenSelector } from './lumen-selector.js';
|
|
7
|
+
import { Button } from './ui/button.js';
|
|
8
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from './ui/dialog.js';
|
|
9
|
+
import { Input } from './ui/input.js';
|
|
10
|
+
import { Label } from './ui/label.js';
|
|
11
|
+
import { FormulaForm, formulaIssueTarget, formulaIssues, formulaOptions } from './view-formulas.js';
|
|
12
|
+
/** A new formula column: label, key, expression, format and roll-up, checked before Add. */
|
|
13
|
+
export function AddFormulaDialog({ open, onOpenChange, domain, formulas, onAdd, }) {
|
|
14
|
+
const fieldKeys = domain.fields.map((f) => f.key);
|
|
15
|
+
const fresh = () => {
|
|
16
|
+
let n = formulas.length + 1;
|
|
17
|
+
while ([...fieldKeys, ...formulas.map((f) => f.key)].includes(`formula_${n}`))
|
|
18
|
+
n++;
|
|
19
|
+
return { key: `formula_${n}`, label: `Formula ${n}`, expr: '', format: 'number', agg: null };
|
|
20
|
+
};
|
|
21
|
+
const [draft, setDraft] = useState(fresh);
|
|
22
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: a fresh draft each time it opens
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (open)
|
|
25
|
+
setDraft(fresh());
|
|
26
|
+
}, [open]);
|
|
27
|
+
const issue = formulaIssues([...formulas, draft], domain)[formulas.length] ?? null;
|
|
28
|
+
const readable = formulaOptions(domain, formulas);
|
|
29
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex max-h-[calc(100svh-2rem)] flex-col gap-0 [--dialog-pad:0px] sm:max-w-2xl max-md:h-svh max-md:max-h-svh max-md:max-w-full max-md:rounded-none", children: [_jsxs(DialogHeader, { className: "border-b px-4 py-3", children: [_jsx(DialogTitle, { className: "text-sm", children: "Add a formula column" }), _jsx(DialogDescription, { children: "A column computed from the other columns, like weight or upside. Saved with the view." })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(FormulaForm, { formula: draft, issue: issue, fields: readable, onChange: (p) => setDraft({ ...draft, ...p }) }) }), _jsxs(DialogFooter, { className: "border-t px-4 py-3", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsx(SaveButton, { label: "Add column", icon: null, issues: issue
|
|
30
|
+
? [
|
|
31
|
+
{
|
|
32
|
+
where: `Formula ${draft.label || draft.key}`,
|
|
33
|
+
message: issue,
|
|
34
|
+
target: formulaIssueTarget(draft.key, issue),
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
: [], onSave: () => onAdd(draft) })] })] }) }));
|
|
38
|
+
}
|
|
39
|
+
/** A Your data column: its name and whether it holds numbers or text. */
|
|
40
|
+
export function YourDataDialog({ open, onOpenChange, keyLabel, busy, error, onAdd, }) {
|
|
41
|
+
const assistantName = useAssistantName();
|
|
42
|
+
const [label, setLabel] = useState('');
|
|
43
|
+
const [type, setType] = useState('number');
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (open) {
|
|
46
|
+
setLabel('');
|
|
47
|
+
setType('number');
|
|
48
|
+
}
|
|
49
|
+
}, [open]);
|
|
50
|
+
const ok = label.trim().length > 0 && label.trim().length <= 40;
|
|
51
|
+
const submit = () => {
|
|
52
|
+
if (ok && !busy)
|
|
53
|
+
onAdd({ label: label.trim(), type });
|
|
54
|
+
};
|
|
55
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { className: "text-sm", children: "Add your own column" }), _jsxs(DialogDescription, { children: ["A column you type into, cell by cell. It stays on every ", keyLabel, " across your cards, and ", assistantName, " reads it but never writes over it."] })] }), _jsxs("form", { className: "grid gap-4", onSubmit: (e) => {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
submit();
|
|
58
|
+
}, children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "yours-label", children: "Name" }), _jsx(Input, { id: "yours-label", autoFocus: true, value: label, maxLength: 40, placeholder: "My target", onChange: (e) => setLabel(e.target.value) })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { children: "Holds" }), _jsx(LumenSelector, { options: [
|
|
59
|
+
{ value: 'number', label: 'Numbers', icon: HashIcon },
|
|
60
|
+
{ value: 'text', label: 'Text', icon: TypeIcon },
|
|
61
|
+
], value: type, onChange: (v) => setType(v) })] }), error && (_jsx("p", { role: "alert", className: "text-destructive text-xs", children: error })), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { type: "submit", size: "sm", disabled: !ok || busy, children: "Add column" })] })] })] }) }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class ApiError extends Error {
|
|
2
|
+
readonly method: string;
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly status: number;
|
|
5
|
+
readonly code?: string;
|
|
6
|
+
constructor(message: string, method: string, url: string, status: number, code?: string);
|
|
7
|
+
}
|
|
8
|
+
/** A JSON request; `where` names the caller in the error log (`assistant.undo`, `workspace.read`). */
|
|
9
|
+
export declare function apiJson<T>(url: string, init?: RequestInit, where?: string): Promise<T>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One JSON fetch for the assistant, workspaces and joins. A failure throws `ApiError` with the
|
|
3
|
+
* method, address, status and the server's `error` and `code`, so a caller shows what failed and
|
|
4
|
+
* Workers Logs keep it: a server fault (5xx) or a network failure is reported to
|
|
5
|
+
* `/api/client-error` with `where` naming the caller. A 4xx is the server saying why it refused,
|
|
6
|
+
* shown to the person and not logged as a fault.
|
|
7
|
+
*/
|
|
8
|
+
import { reportError } from './route-error.js';
|
|
9
|
+
export class ApiError extends Error {
|
|
10
|
+
method;
|
|
11
|
+
url;
|
|
12
|
+
status;
|
|
13
|
+
code;
|
|
14
|
+
constructor(message, method, url, status, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.method = method;
|
|
17
|
+
this.url = url;
|
|
18
|
+
this.name = 'ApiError';
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** A JSON request; `where` names the caller in the error log (`assistant.undo`, `workspace.read`). */
|
|
24
|
+
export async function apiJson(url, init = {}, where = 'api') {
|
|
25
|
+
const method = (init.method ?? 'GET').toUpperCase();
|
|
26
|
+
let res;
|
|
27
|
+
try {
|
|
28
|
+
res = await fetch(url, {
|
|
29
|
+
...init,
|
|
30
|
+
headers: {
|
|
31
|
+
accept: 'application/json',
|
|
32
|
+
...(init.body ? { 'content-type': 'application/json' } : {}),
|
|
33
|
+
...(init.headers ?? {}),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (err.name === 'AbortError')
|
|
39
|
+
throw err;
|
|
40
|
+
const e = new ApiError(`Couldn't reach the server (${method} ${url})`, method, url, 0);
|
|
41
|
+
reportError(e, 'shown', where);
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
const text = await res.text();
|
|
45
|
+
let body = null;
|
|
46
|
+
try {
|
|
47
|
+
body = text ? JSON.parse(text) : null;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
body = null;
|
|
51
|
+
}
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const b = (body ?? {});
|
|
54
|
+
const e = new ApiError(b.error ?? `${method} ${url} answered ${res.status}`, method, url, res.status, b.code);
|
|
55
|
+
if (res.status >= 500)
|
|
56
|
+
reportError(e, 'shown', where);
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
return body;
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type LucideIcon } from 'lucide-react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
/** One app of the product. */
|
|
4
|
+
export interface LumenApp {
|
|
5
|
+
id: string;
|
|
6
|
+
/** Its plain name, for the collapsed sidebar's tooltip. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** How its name is drawn (e.g. the app's accent, then a shared suffix); `name` when left out. */
|
|
9
|
+
label?: ReactNode;
|
|
10
|
+
icon: LucideIcon;
|
|
11
|
+
href: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function LumenAppSwitcher({ current, apps, product, menuLabel, }: {
|
|
14
|
+
/** The app this page belongs to; it leads `apps` even when the menu leaves it out. */
|
|
15
|
+
current: LumenApp;
|
|
16
|
+
/** The apps the menu lists, in order. */
|
|
17
|
+
apps: LumenApp[];
|
|
18
|
+
/** The product's name under the app's. */
|
|
19
|
+
product?: string;
|
|
20
|
+
menuLabel?: string;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// The app switcher (in Lumen for any product with more than one app): the
|
|
3
|
+
// sidebar's top shows the current app and opens a menu of the product's apps. LumenShell's `brand`
|
|
4
|
+
// takes it; the product passes the apps the person can open.
|
|
5
|
+
import { ChevronsUpDownIcon } from 'lucide-react';
|
|
6
|
+
import { cn } from './lib/utils.js';
|
|
7
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from './ui/dropdown-menu.js';
|
|
8
|
+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from './ui/sidebar.js';
|
|
9
|
+
export function LumenAppSwitcher({ current, apps, product, menuLabel = 'Apps', }) {
|
|
10
|
+
const { isMobile } = useSidebar();
|
|
11
|
+
return (_jsx(SidebarMenu, { children: _jsx(SidebarMenuItem, { children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(SidebarMenuButton, { size: "lg", tooltip: current.name, className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground", children: [_jsx("div", { className: "flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground", children: _jsx(current.icon, { className: "size-4" }) }), _jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [_jsx("span", { className: "truncate font-semibold", children: current.label ?? current.name }), product && (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: product }))] }), _jsx(ChevronsUpDownIcon, { className: "ml-auto" })] }) }), _jsxs(DropdownMenuContent, { className: "min-w-56 rounded-lg", align: "start", side: isMobile ? 'bottom' : 'right', sideOffset: 4, children: [_jsx(DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: menuLabel }), apps.map((a) => (_jsx(DropdownMenuItem, { asChild: true, className: cn('gap-2 p-2', a.id === current.id && 'bg-muted'), children: _jsxs("a", { href: a.href, "aria-current": a.id === current.id ? 'page' : undefined, children: [_jsx("div", { className: "flex size-6 items-center justify-center rounded-md border", children: _jsx(a.icon, { className: "size-3.5 shrink-0" }) }), a.label ?? a.name] }) }, a.id)))] })] }) }) }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CustomView, CustomViewConfig } from '@columenlabs/lumen-ui-types/views';
|
|
2
|
+
/** What a page passes to LumenLayout for an app view: the view as this person has it, its save and its reset. */
|
|
3
|
+
/** An app view as `appView` gives it, or as an app keeps it with its dates. */
|
|
4
|
+
type AnyView = Omit<CustomView, 'created_at' | 'updated_at'>;
|
|
5
|
+
export interface AppViewState<V extends AnyView> {
|
|
6
|
+
view: V | undefined;
|
|
7
|
+
/** Saves the person's version of the page. */
|
|
8
|
+
onConfigChange?: (config: CustomViewConfig) => void;
|
|
9
|
+
/** Puts the page back to its default; only once the person has changed it. */
|
|
10
|
+
onReset?: () => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The person's version of `app` (an app view from `appView`, undefined when the page shows another
|
|
14
|
+
* view): drawn from the code at once, then from the server once read. `enabled` false keeps the
|
|
15
|
+
* code's (a page that is a workspace tab, an app without the store).
|
|
16
|
+
*/
|
|
17
|
+
export declare function useAppView<V extends AnyView>(app: V | undefined, enabled?: boolean): AppViewState<V>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { apiJson } from './api-fetch.js';
|
|
3
|
+
const BASE = '/api/app-views';
|
|
4
|
+
/**
|
|
5
|
+
* The person's version of `app` (an app view from `appView`, undefined when the page shows another
|
|
6
|
+
* view): drawn from the code at once, then from the server once read. `enabled` false keeps the
|
|
7
|
+
* code's (a page that is a workspace tab, an app without the store).
|
|
8
|
+
*/
|
|
9
|
+
export function useAppView(app, enabled = true) {
|
|
10
|
+
const qc = useQueryClient();
|
|
11
|
+
const url = app && `${BASE}/${app.domain}/${app.id}`;
|
|
12
|
+
const key = ['app-view', app?.domain, app?.id];
|
|
13
|
+
const q = useQuery({
|
|
14
|
+
queryKey: key,
|
|
15
|
+
queryFn: () => apiJson(url, {}, 'app_view.read'),
|
|
16
|
+
enabled: !!app && enabled,
|
|
17
|
+
});
|
|
18
|
+
const put = (v) => qc.setQueryData(key, v);
|
|
19
|
+
const save = useMutation({
|
|
20
|
+
mutationFn: (config) => apiJson(url, { method: 'PUT', body: JSON.stringify({ config }) }, 'app_view.save'),
|
|
21
|
+
onMutate: (config) => {
|
|
22
|
+
const now = q.data ?? app;
|
|
23
|
+
if (now)
|
|
24
|
+
put({ ...now, config: config, customized: true });
|
|
25
|
+
},
|
|
26
|
+
onSuccess: put,
|
|
27
|
+
onError: () => qc.invalidateQueries({ queryKey: key }),
|
|
28
|
+
});
|
|
29
|
+
const reset = useMutation({
|
|
30
|
+
mutationFn: () => apiJson(url, { method: 'DELETE' }, 'app_view.reset'),
|
|
31
|
+
onSuccess: put,
|
|
32
|
+
});
|
|
33
|
+
if (!app || !enabled)
|
|
34
|
+
return { view: app };
|
|
35
|
+
const view = q.data ?? app;
|
|
36
|
+
return {
|
|
37
|
+
view,
|
|
38
|
+
onConfigChange: (config) => save.mutate(config),
|
|
39
|
+
onReset: view.customized ? () => reset.mutate() : undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AssistantPageType } from './assistant-screen.js';
|
|
2
|
+
/** What one part of the screen tells the assistant: a short label for the chip and data for the model. */
|
|
3
|
+
export interface AssistantNote {
|
|
4
|
+
label: string;
|
|
5
|
+
data?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* What the person points at with @: the page, a card on it, a page of the app (a symbol, a
|
|
9
|
+
* screener, a saved view...), a workspace or one of its tabs, or a Settings section. The assistant reads
|
|
10
|
+
* what each is and how to reach its data.
|
|
11
|
+
*/
|
|
12
|
+
export type AssistantRef = {
|
|
13
|
+
kind: 'page';
|
|
14
|
+
label: string;
|
|
15
|
+
path: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'card';
|
|
18
|
+
label: string;
|
|
19
|
+
id: string;
|
|
20
|
+
card?: string;
|
|
21
|
+
where?: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'view';
|
|
24
|
+
label: string;
|
|
25
|
+
path: string;
|
|
26
|
+
type?: AssistantPageType;
|
|
27
|
+
where?: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'workspace';
|
|
30
|
+
label: string;
|
|
31
|
+
workspace: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'tab';
|
|
34
|
+
label: string;
|
|
35
|
+
workspace: string;
|
|
36
|
+
tab: string;
|
|
37
|
+
where?: string;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'setting';
|
|
40
|
+
label: string;
|
|
41
|
+
section: string;
|
|
42
|
+
};
|
|
43
|
+
/** Where a note comes from: the page, or the view on it. */
|
|
44
|
+
export type AssistantSlot = 'page' | 'view';
|
|
45
|
+
/** What pages may call: the same functions for the life of the shell, so pages never redraw. */
|
|
46
|
+
export interface AssistantActions {
|
|
47
|
+
/** The assistant's name, as people see it: `Atlas`. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** The assistant's API base on this app (workspaces and frames live under it). */
|
|
50
|
+
api: string;
|
|
51
|
+
note: (slot: AssistantSlot, note: AssistantNote | null) => void;
|
|
52
|
+
loadChat: (id: string) => void;
|
|
53
|
+
setOpen: (open: boolean) => void;
|
|
54
|
+
/** The assistant answers on this app (its status loaded): cards show ✦ Ask the assistant only then. */
|
|
55
|
+
ready: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Opens the panel with the card's @ chips and a suggested message in the composer, for the person
|
|
58
|
+
* to edit and send (a card's ⋯ ▸ Ask the assistant; never sent on the tap).
|
|
59
|
+
*/
|
|
60
|
+
draft: (text: string, refs: AssistantRef[]) => void;
|
|
61
|
+
}
|
|
62
|
+
export declare const AssistantContext: import("react").Context<AssistantActions | null>;
|
|
63
|
+
/** The assistant's stable actions for pages (open a chat, open the panel); null where the app has no assistant. */
|
|
64
|
+
export declare function useAssistant(): AssistantActions | null;
|
|
65
|
+
/** A page (or one part of it) tells the assistant what the person sees; cleared when it unmounts. */
|
|
66
|
+
export declare function useAssistantNote(slot: AssistantSlot, note: AssistantNote | null): void;
|
|
67
|
+
/** The docked assistant's name, or `fallback` where the app has none. */
|
|
68
|
+
export declare function useAssistantName(fallback?: string): string;
|
|
69
|
+
/** True while the app is still finding out whether its assistant answers: a page that needs it
|
|
70
|
+
* waits instead of saying it is missing. */
|
|
71
|
+
export declare const AssistantPendingContext: import("react").Context<boolean>;
|
|
72
|
+
export declare function useAssistantPending(): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// The assistant's place in Lumen: the contract pages and cards use to talk to an
|
|
2
|
+
// assistant, with nothing of the assistant itself. A product that docks one (the product's assistant panel)
|
|
3
|
+
// provides `AssistantContext`; without it every hook here is a no-op and cards hide Ask the assistant.
|
|
4
|
+
import { createContext, useContext, useEffect } from 'react';
|
|
5
|
+
export const AssistantContext = createContext(null);
|
|
6
|
+
/** The assistant's stable actions for pages (open a chat, open the panel); null where the app has no assistant. */
|
|
7
|
+
export function useAssistant() {
|
|
8
|
+
return useContext(AssistantContext);
|
|
9
|
+
}
|
|
10
|
+
/** A page (or one part of it) tells the assistant what the person sees; cleared when it unmounts. */
|
|
11
|
+
export function useAssistantNote(slot, note) {
|
|
12
|
+
const set = useContext(AssistantContext)?.note;
|
|
13
|
+
const key = note ? JSON.stringify(note) : '';
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!set)
|
|
16
|
+
return;
|
|
17
|
+
set(slot, key ? JSON.parse(key) : null);
|
|
18
|
+
return () => set(slot, null);
|
|
19
|
+
}, [set, slot, key]);
|
|
20
|
+
}
|
|
21
|
+
/** The docked assistant's name, or `fallback` where the app has none. */
|
|
22
|
+
export function useAssistantName(fallback = 'the assistant') {
|
|
23
|
+
return useContext(AssistantContext)?.name ?? fallback;
|
|
24
|
+
}
|
|
25
|
+
/** True while the app is still finding out whether its assistant answers: a page that needs it
|
|
26
|
+
* waits instead of saying it is missing. */
|
|
27
|
+
export const AssistantPendingContext = createContext(false);
|
|
28
|
+
export function useAssistantPending() {
|
|
29
|
+
return useContext(AssistantPendingContext);
|
|
30
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** A value kept for this browser tab (sessionStorage), read with `use` and set with `write`. */
|
|
3
|
+
export declare function tabState<T>(key: string, fallback: T, parse: (raw: string) => T | null): {
|
|
4
|
+
write: (next: T) => void;
|
|
5
|
+
use: () => T;
|
|
6
|
+
get: () => T;
|
|
7
|
+
};
|
|
8
|
+
/** Whether the assistant is open in this tab. */
|
|
9
|
+
export declare const assistantOpen: {
|
|
10
|
+
write: (next: boolean) => void;
|
|
11
|
+
use: () => boolean;
|
|
12
|
+
get: () => boolean;
|
|
13
|
+
};
|
|
14
|
+
/** ⌘J (Ctrl+J) calls `toggle` anywhere in the app. */
|
|
15
|
+
export declare function useAssistantHotkey(toggle: () => void): void;
|
|
16
|
+
/** The page header's ✦ button: shown while the assistant is closed, with a dot when a reply came. */
|
|
17
|
+
export declare function LumenAssistantButton({ name, open, unread, onOpen, className, }: {
|
|
18
|
+
name: string;
|
|
19
|
+
open: boolean;
|
|
20
|
+
unread?: boolean;
|
|
21
|
+
onOpen: () => void;
|
|
22
|
+
className?: string;
|
|
23
|
+
}): import("react").JSX.Element | null;
|
|
24
|
+
/**
|
|
25
|
+
* Docked right of the page on desktop (nothing while closed); a full-height sheet on phone, with
|
|
26
|
+
* `closedBar` under the page while it is closed. `children` draws the body; it is told which.
|
|
27
|
+
*/
|
|
28
|
+
export declare function LumenAssistantPanel({ name, open, onOpenChange, closedBar, children, }: {
|
|
29
|
+
name: string;
|
|
30
|
+
open: boolean;
|
|
31
|
+
onOpenChange: (open: boolean) => void;
|
|
32
|
+
/** Shown on phone while closed (the product's one-line screen-move bar). */
|
|
33
|
+
closedBar?: ReactNode;
|
|
34
|
+
children: (phone: boolean) => ReactNode;
|
|
35
|
+
}): import("react").JSX.Element | null;
|
|
36
|
+
/** One chat in the history menu. */
|
|
37
|
+
export interface AssistantChat {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
/** ISO time of its last message. */
|
|
41
|
+
updated_at: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Chats, a dropdown from the panel header: chats grouped by when they were
|
|
45
|
+
* last used, searched by title. A row is read ahead when highlighted, so picking it opens at once.
|
|
46
|
+
*/
|
|
47
|
+
export declare function LumenAssistantChats<C extends AssistantChat>({ chats, value, note, disabled, onOpen, onRefresh, onHighlight, }: {
|
|
48
|
+
/** null while loading. */
|
|
49
|
+
chats: C[] | null;
|
|
50
|
+
value?: string;
|
|
51
|
+
/** A line above the list: how long chats are kept. */
|
|
52
|
+
note?: ReactNode;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
onOpen: (chat: C) => void;
|
|
55
|
+
/** The menu opened: read the list again. */
|
|
56
|
+
onRefresh?: () => void;
|
|
57
|
+
onHighlight?: (chat: C) => void;
|
|
58
|
+
}): import("react").JSX.Element;
|