@columenlabs/lumen-ui 0.2.1 → 0.3.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.
- package/dist/confirm.d.ts +4 -2
- package/dist/confirm.js +16 -2
- package/dist/connections.js +6 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/lumen-auth.d.ts +67 -0
- package/dist/lumen-auth.js +52 -0
- package/dist/lumen-choice.d.ts +18 -0
- package/dist/lumen-choice.js +12 -0
- package/dist/lumen-secret.d.ts +8 -0
- package/dist/lumen-secret.js +21 -0
- package/dist/view-editor.js +2 -2
- package/package.json +2 -2
package/dist/confirm.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
2
|
export interface LumenConfirmProps {
|
|
3
3
|
/** The question, e.g. `Delete view Options?`. */
|
|
4
4
|
title: ReactNode;
|
|
@@ -14,5 +14,7 @@ export interface LumenConfirmProps {
|
|
|
14
14
|
onOpenChange?: (open: boolean) => void;
|
|
15
15
|
/** False for a confirm that destroys nothing (a new version): its action is not red. */
|
|
16
16
|
destructive?: boolean;
|
|
17
|
+
/** A word the person must type before the action unlocks, for a step that loses a lot (`DELETE`). */
|
|
18
|
+
typeToConfirm?: string;
|
|
17
19
|
}
|
|
18
|
-
export declare function LumenConfirm({ title, description, action, cancel, onConfirm, children, open, onOpenChange, destructive, }: LumenConfirmProps): import("react").JSX.Element;
|
|
20
|
+
export declare function LumenConfirm({ title, description, action, cancel, onConfirm, children, open, onOpenChange, destructive, typeToConfirm, }: LumenConfirmProps): import("react").JSX.Element;
|
package/dist/confirm.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Every delete asks first: one shadcn AlertDialog every app uses for any
|
|
3
|
+
// delete, remove, cancel or other step to confirm (LumenConfirm, : no raw
|
|
4
|
+
// AlertDialog in an app). Wrap the trigger button, or control `open` when it starts from a menu item.
|
|
5
|
+
import { useId, useState } from 'react';
|
|
2
6
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from './ui/alert-dialog.js';
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
import { Field, FieldLabel } from './ui/field.js';
|
|
8
|
+
import { Input } from './ui/input.js';
|
|
9
|
+
export function LumenConfirm({ title, description = 'This cannot be undone.', action = 'Delete', cancel = 'Cancel', onConfirm, children, open, onOpenChange, destructive = true, typeToConfirm, }) {
|
|
10
|
+
const id = useId();
|
|
11
|
+
const [typed, setTyped] = useState('');
|
|
12
|
+
const locked = typeToConfirm !== undefined && typed !== typeToConfirm;
|
|
13
|
+
const change = (o) => {
|
|
14
|
+
if (!o)
|
|
15
|
+
setTyped('');
|
|
16
|
+
onOpenChange?.(o);
|
|
17
|
+
};
|
|
18
|
+
return (_jsxs(AlertDialog, { open: open, onOpenChange: change, children: [children && _jsx(AlertDialogTrigger, { asChild: true, children: children }), _jsxs(AlertDialogContent, { size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: title }), _jsx(AlertDialogDescription, { children: description })] }), typeToConfirm !== undefined && (_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: id, children: _jsxs("span", { children: ["Type ", _jsx("code", { className: "rounded bg-muted px-1.5 font-code", children: typeToConfirm }), " to confirm"] }) }), _jsx(Input, { id: id, autoComplete: "off", className: "font-code", value: typed, onChange: (e) => setTyped(e.target.value) })] })), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: cancel }), _jsx(AlertDialogAction, { variant: destructive ? 'destructive-confirm' : 'default', disabled: locked, onClick: onConfirm, children: action })] })] })] }));
|
|
5
19
|
}
|
package/dist/connections.js
CHANGED
|
@@ -143,7 +143,12 @@ export function connectionFields(domain, connections, read, taken) {
|
|
|
143
143
|
// field on each side is picked; its group stays in the tree for that.
|
|
144
144
|
if (!on) {
|
|
145
145
|
sources.push({ id, name, state: 'off', detail: NOT_LINKED });
|
|
146
|
-
groups.push({
|
|
146
|
+
groups.push({
|
|
147
|
+
id,
|
|
148
|
+
label: name,
|
|
149
|
+
color: LINK_COLOR,
|
|
150
|
+
note: 'Pick a field on each side to link it',
|
|
151
|
+
});
|
|
147
152
|
continue;
|
|
148
153
|
}
|
|
149
154
|
const { index, ok } = indexOf(to, on);
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export { LumenEChart, type EChartOption, type ChartTokens, axisStyle, baseOption
|
|
|
15
15
|
export { LumenGeoMap } from './lumen-geo-map.js';
|
|
16
16
|
export { LumenWidget, type CardControls, type CardLink, LumenSavedWidget } from './lumen-widget.js';
|
|
17
17
|
export { LumenConfirm, type LumenConfirmProps } from './confirm.js';
|
|
18
|
+
export { type LumenAuthPoint, LumenAuthHeading, LumenAuthNote, LumenAuthProblem, LumenAuthStage, type LumenAuthStageProps, LumenCodeField, LumenEmailField, LumenPasswordField, LumenStepBar, } from './lumen-auth.js';
|
|
19
|
+
export { LumenChoice, type LumenChoiceOption } from './lumen-choice.js';
|
|
20
|
+
export { LumenSecret } from './lumen-secret.js';
|
|
21
|
+
export { RankedBars, StackedBars } from './lumen-widget-charts.js';
|
|
18
22
|
export { COVERAGE_LEVELS, CoverageCell, type CoverageJobState, type CoverageLevel, CoverageLegend, coverageClass, coverageLabel, coverageLevel, } from './coverage-cells.js';
|
|
19
23
|
export { type CoverageBlock, type CoverageData, type CoverageFact, type CoveragePick, type CoverageRow, type CoverageSummary, LumenCoverageMap, type LumenCoverageMapProps, blockName, blockOf, } from './coverage-map.js';
|
|
20
24
|
export { type CalendarFact, LumenCoverageCalendarMap, type LumenCoverageCalendarMapProps, } from './coverage-calendar-map.js';
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,10 @@ export { LumenEChart, axisStyle, baseOption, useChartTokens, } from './lumen-ech
|
|
|
22
22
|
export { LumenGeoMap } from './lumen-geo-map.js';
|
|
23
23
|
export { LumenWidget, LumenSavedWidget } from './lumen-widget.js';
|
|
24
24
|
export { LumenConfirm } from './confirm.js';
|
|
25
|
+
export { LumenAuthHeading, LumenAuthNote, LumenAuthProblem, LumenAuthStage, LumenCodeField, LumenEmailField, LumenPasswordField, LumenStepBar, } from './lumen-auth.js';
|
|
26
|
+
export { LumenChoice } from './lumen-choice.js';
|
|
27
|
+
export { LumenSecret } from './lumen-secret.js';
|
|
28
|
+
export { RankedBars, StackedBars } from './lumen-widget-charts.js';
|
|
25
29
|
export { COVERAGE_LEVELS, CoverageCell, CoverageLegend, coverageClass, coverageLabel, coverageLevel, } from './coverage-cells.js';
|
|
26
30
|
export { LumenCoverageMap, blockName, blockOf, } from './coverage-map.js';
|
|
27
31
|
export { LumenCoverageCalendarMap, } from './coverage-calendar-map.js';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
export interface LumenAuthPoint {
|
|
3
|
+
icon: ComponentType<{
|
|
4
|
+
className?: string;
|
|
5
|
+
}>;
|
|
6
|
+
title: string;
|
|
7
|
+
body: string;
|
|
8
|
+
}
|
|
9
|
+
export interface LumenAuthStageProps {
|
|
10
|
+
/** The app's logo and name, shown over the story and, on a phone, over the form. */
|
|
11
|
+
brand: ReactNode;
|
|
12
|
+
/** The story's headline and the line under it. */
|
|
13
|
+
pitch: {
|
|
14
|
+
title: ReactNode;
|
|
15
|
+
body?: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
points?: LumenAuthPoint[];
|
|
18
|
+
/** A picture under the points, such as a diagram of what the app does. */
|
|
19
|
+
illustration?: ReactNode;
|
|
20
|
+
/** Small links under the form (privacy, terms). */
|
|
21
|
+
footer?: ReactNode;
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
/** The page a signed-out screen sits in. */
|
|
25
|
+
export declare function LumenAuthStage({ brand, pitch, points, illustration, footer, children, }: LumenAuthStageProps): import("react").JSX.Element;
|
|
26
|
+
/** A screen's title, and a line under it. */
|
|
27
|
+
export declare function LumenAuthHeading({ title, sub }: {
|
|
28
|
+
title: ReactNode;
|
|
29
|
+
sub?: ReactNode;
|
|
30
|
+
}): import("react").JSX.Element;
|
|
31
|
+
/** A red line over the form; renders nothing when empty. */
|
|
32
|
+
export declare function LumenAuthProblem({ children }: {
|
|
33
|
+
children?: ReactNode;
|
|
34
|
+
}): import("react").JSX.Element | null;
|
|
35
|
+
/** A plain note over the form, such as "New code sent"; renders nothing when empty. */
|
|
36
|
+
export declare function LumenAuthNote({ children }: {
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}): import("react").JSX.Element | null;
|
|
39
|
+
interface TextFieldProps {
|
|
40
|
+
id?: string;
|
|
41
|
+
value: string;
|
|
42
|
+
onChange: (v: string) => void;
|
|
43
|
+
/** The browser's autocomplete hint (`username`, `email`, `current-password`, `new-password`). */
|
|
44
|
+
autoComplete: string;
|
|
45
|
+
label?: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function LumenEmailField({ id, value, onChange, autoComplete, label, }: TextFieldProps): import("react").JSX.Element;
|
|
48
|
+
/** A password box with a Show switch beside it. */
|
|
49
|
+
export declare function LumenPasswordField({ id, value, onChange, autoComplete, label, }: TextFieldProps): import("react").JSX.Element;
|
|
50
|
+
/** A one-time code from an email or an authenticator app: digits only, big and spaced. */
|
|
51
|
+
export declare function LumenCodeField({ id, value, onChange, length, error, hint, onResend, resendLabel, }: {
|
|
52
|
+
id?: string;
|
|
53
|
+
value: string;
|
|
54
|
+
onChange: (v: string) => void;
|
|
55
|
+
length?: number;
|
|
56
|
+
error?: ReactNode;
|
|
57
|
+
hint?: ReactNode;
|
|
58
|
+
/** Shows a link after the hint that asks for a fresh code. */
|
|
59
|
+
onResend?: () => void;
|
|
60
|
+
resendLabel?: string;
|
|
61
|
+
}): import("react").JSX.Element;
|
|
62
|
+
/** Progress through a short flow: one bar per step, filled up to `at`, and "Step 2 of 3". */
|
|
63
|
+
export declare function LumenStepBar({ at, of }: {
|
|
64
|
+
at: number;
|
|
65
|
+
of: number;
|
|
66
|
+
}): import("react").JSX.Element;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// The signed-out screens every app shares: a split page (the app's story on the left, hidden on
|
|
3
|
+
// a phone, the form on the right) and the fields its forms are built from. The app passes its
|
|
4
|
+
// brand, pitch, points, illustration and footer links; the kit draws the layout.
|
|
5
|
+
import { Lock, Mail } from 'lucide-react';
|
|
6
|
+
import { useId, useState } from 'react';
|
|
7
|
+
import { Alert, AlertDescription } from './ui/alert.js';
|
|
8
|
+
import { Checkbox } from './ui/checkbox.js';
|
|
9
|
+
import { Field, FieldDescription, FieldError, FieldLabel } from './ui/field.js';
|
|
10
|
+
import { Input } from './ui/input.js';
|
|
11
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from './ui/input-group.js';
|
|
12
|
+
/** The page a signed-out screen sits in. */
|
|
13
|
+
export function LumenAuthStage({ brand, pitch, points = [], illustration, footer, children, }) {
|
|
14
|
+
return (_jsxs("div", { className: "grid min-h-svh grid-cols-1 lg:grid-cols-[minmax(0,1.05fr)_minmax(0,1fr)]", children: [_jsxs("aside", { className: "hidden flex-col gap-10 border-r bg-sidebar px-10 py-8 text-sidebar-foreground lg:flex", children: [brand, _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("h2", { className: "text-balance font-semibold text-3xl leading-tight", children: pitch.title }), pitch.body && _jsx("p", { className: "max-w-md text-muted-foreground", children: pitch.body })] }), points.length > 0 && (_jsx("ul", { className: "flex flex-col gap-4", children: points.map(({ icon: Icon, title, body }) => (_jsxs("li", { className: "flex gap-3", children: [_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: _jsx(Icon, { className: "size-4.5" }) }), _jsxs("span", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium text-sm", children: title }), _jsx("span", { className: "text-muted-foreground text-sm", children: body })] })] }, title))) })), illustration] }), _jsxs("main", { className: "flex flex-col bg-background px-6 py-8 sm:px-12", children: [_jsx("div", { className: "lg:hidden", children: brand }), _jsx("div", { className: "mx-auto flex w-full max-w-sm flex-1 flex-col justify-center py-8", children: children }), footer && _jsx("div", { className: "text-center text-muted-foreground text-xs", children: footer })] })] }));
|
|
15
|
+
}
|
|
16
|
+
/** A screen's title, and a line under it. */
|
|
17
|
+
export function LumenAuthHeading({ title, sub }) {
|
|
18
|
+
return (_jsxs("div", { className: "mb-6 flex flex-col gap-1.5", children: [_jsx("h1", { className: "font-semibold text-2xl", children: title }), sub && _jsx("p", { className: "text-muted-foreground text-sm", children: sub })] }));
|
|
19
|
+
}
|
|
20
|
+
/** A red line over the form; renders nothing when empty. */
|
|
21
|
+
export function LumenAuthProblem({ children }) {
|
|
22
|
+
if (!children)
|
|
23
|
+
return null;
|
|
24
|
+
return (_jsx(Alert, { variant: "destructive", className: "mb-4", children: _jsx(AlertDescription, { children: children }) }));
|
|
25
|
+
}
|
|
26
|
+
/** A plain note over the form, such as "New code sent"; renders nothing when empty. */
|
|
27
|
+
export function LumenAuthNote({ children }) {
|
|
28
|
+
if (!children)
|
|
29
|
+
return null;
|
|
30
|
+
return (_jsxs(Alert, { className: "mb-4", children: [_jsx(Mail, {}), _jsx(AlertDescription, { children: children })] }));
|
|
31
|
+
}
|
|
32
|
+
export function LumenEmailField({ id, value, onChange, autoComplete, label = 'Email', }) {
|
|
33
|
+
const own = useId();
|
|
34
|
+
const fieldId = id ?? own;
|
|
35
|
+
return (_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: fieldId, children: label }), _jsxs(InputGroup, { className: "h-10", children: [_jsx(InputGroupAddon, { children: _jsx(Mail, {}) }), _jsx(InputGroupInput, { id: fieldId, type: "email", required: true, autoComplete: autoComplete, placeholder: "you@example.com", value: value, onChange: (e) => onChange(e.target.value) })] })] }));
|
|
36
|
+
}
|
|
37
|
+
/** A password box with a Show switch beside it. */
|
|
38
|
+
export function LumenPasswordField({ id, value, onChange, autoComplete, label = 'Password', }) {
|
|
39
|
+
const own = useId();
|
|
40
|
+
const fieldId = id ?? own;
|
|
41
|
+
const [show, setShow] = useState(false);
|
|
42
|
+
return (_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: fieldId, children: label }), _jsxs(InputGroup, { className: "h-10", children: [_jsx(InputGroupAddon, { children: _jsx(Lock, {}) }), _jsx(InputGroupInput, { id: fieldId, type: show ? 'text' : 'password', required: true, autoComplete: autoComplete, value: value, onChange: (e) => onChange(e.target.value) }), _jsx(InputGroupAddon, { align: "inline-end", className: "border-l bg-muted/50 px-3", children: _jsxs("label", { htmlFor: `${fieldId}-show`, className: "flex items-center gap-1.5 text-sm", children: [_jsx(Checkbox, { id: `${fieldId}-show`, checked: show, onCheckedChange: (v) => setShow(v === true) }), ' ', "Show"] }) })] })] }));
|
|
43
|
+
}
|
|
44
|
+
/** A one-time code from an email or an authenticator app: digits only, big and spaced. */
|
|
45
|
+
export function LumenCodeField({ id, value, onChange, length = 6, error, hint, onResend, resendLabel = 'Send a new code', }) {
|
|
46
|
+
const own = useId();
|
|
47
|
+
return (_jsxs(Field, { "data-invalid": error ? true : undefined, children: [_jsx(Input, { id: id ?? own, inputMode: "numeric", autoComplete: "one-time-code", placeholder: '0'.repeat(length), maxLength: length, "aria-label": `${length}-digit code`, className: "h-14 text-center font-code text-2xl tracking-[0.6em]", value: value, onChange: (e) => onChange(e.target.value.replace(/\D/g, '').slice(0, length)), "aria-invalid": Boolean(error) }), error ? _jsx(FieldError, { children: error }) : null, (hint || onResend) && (_jsxs(FieldDescription, { className: "text-center", children: [hint, ' ', onResend && (_jsx("button", { type: "button", className: "text-primary underline-offset-4 hover:underline", onClick: onResend, children: resendLabel }))] }))] }));
|
|
48
|
+
}
|
|
49
|
+
/** Progress through a short flow: one bar per step, filled up to `at`, and "Step 2 of 3". */
|
|
50
|
+
export function LumenStepBar({ at, of }) {
|
|
51
|
+
return (_jsxs("div", { className: "mb-5 flex flex-col gap-1.5", children: [_jsx("div", { className: "flex gap-1.5", children: Array.from({ length: of }, (_, i) => i + 1).map((n) => (_jsx("span", { className: `h-1 flex-1 rounded-full ${n <= at ? 'bg-primary' : 'bg-muted'}` }, n))) }), _jsxs("span", { className: "text-center text-muted-foreground text-xs", children: ["Step ", at, " of ", of] })] }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
export interface LumenChoiceOption<T extends string> {
|
|
3
|
+
value: T;
|
|
4
|
+
icon: ComponentType<{
|
|
5
|
+
className?: string;
|
|
6
|
+
}>;
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
description?: ReactNode;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function LumenChoice<T extends string>({ label, value, options, onValueChange, }: {
|
|
12
|
+
/** Names the group for screen readers. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** The picked option, or null before the person picks. */
|
|
15
|
+
value: T | null;
|
|
16
|
+
options: LumenChoiceOption<T>[];
|
|
17
|
+
onValueChange: (v: T) => void;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// A pick-one list of cards, each with an icon, a title and a line under it: the way an app asks
|
|
3
|
+
// "how do you want to do this?" when each option needs a sentence. For a short one-of-a-few
|
|
4
|
+
// choice without notes, use CardSegments.
|
|
5
|
+
import { Check } from 'lucide-react';
|
|
6
|
+
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from './ui/item.js';
|
|
7
|
+
export function LumenChoice({ label, value, options, onValueChange, }) {
|
|
8
|
+
return (_jsx("fieldset", { "aria-label": label, className: "m-0 flex min-w-0 flex-col gap-3 border-0 p-0", children: options.map(({ value: v, icon: Icon, title, description, disabled }) => {
|
|
9
|
+
const picked = v === value;
|
|
10
|
+
return (_jsx(Item, { asChild: true, variant: "outline", className: picked ? 'border-primary bg-primary/5' : 'disabled:opacity-50', children: _jsxs("button", { type: "button", "aria-pressed": picked, disabled: disabled, onClick: () => onValueChange(v), children: [_jsx(ItemMedia, { variant: "icon", children: _jsx(Icon, { className: picked ? 'text-primary' : undefined }) }), _jsxs(ItemContent, { className: "text-left", children: [_jsx(ItemTitle, { children: title }), description && _jsx(ItemDescription, { children: description })] }), picked && _jsx(Check, { className: "size-5 shrink-0 text-primary" })] }) }, v));
|
|
11
|
+
}) }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare function LumenSecret({ value, label, warning, hidden, }: {
|
|
3
|
+
value: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
warning?: ReactNode;
|
|
6
|
+
/** Start with the value masked. */
|
|
7
|
+
hidden?: boolean;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// A secret the app shows only once (a new API key, a signing secret): a warning to copy it now,
|
|
3
|
+
// the value in code type with a Show switch, and a Copy button that says when it worked.
|
|
4
|
+
import { Check, Copy, Eye, EyeOff, TriangleAlert } from 'lucide-react';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { Alert, AlertDescription } from './ui/alert.js';
|
|
7
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from './ui/input-group.js';
|
|
8
|
+
export function LumenSecret({ value, label = 'Secret', warning = 'Copy it now. It won’t be shown again.', hidden = false, }) {
|
|
9
|
+
const [show, setShow] = useState(!hidden);
|
|
10
|
+
const [copied, setCopied] = useState(false);
|
|
11
|
+
const copy = async () => {
|
|
12
|
+
try {
|
|
13
|
+
await navigator.clipboard.writeText(value);
|
|
14
|
+
setCopied(true);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
setCopied(false);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [warning && (_jsxs(Alert, { children: [_jsx(TriangleAlert, {}), _jsx(AlertDescription, { children: warning })] })), _jsxs(InputGroup, { className: "h-10", children: [_jsx(InputGroupInput, { readOnly: true, "aria-label": label, type: show ? 'text' : 'password', value: value, className: "font-code text-xs", onFocus: (e) => e.currentTarget.select() }), _jsxs(InputGroupAddon, { align: "inline-end", children: [_jsx(InputGroupButton, { size: "icon-xs", "aria-label": show ? `Hide ${label.toLowerCase()}` : `Show ${label.toLowerCase()}`, onClick: () => setShow((s) => !s), children: show ? _jsx(EyeOff, {}) : _jsx(Eye, {}) }), _jsxs(InputGroupButton, { size: "xs", onClick: copy, children: [copied ? _jsx(Check, {}) : _jsx(Copy, {}), copied ? 'Copied' : 'Copy'] })] })] })] }));
|
|
21
|
+
}
|
package/dist/view-editor.js
CHANGED
|
@@ -10,7 +10,7 @@ import { CONNECTED_COUNT, VIEW_CARD_KIND_NAMES, WIDGET_STYLES, cardsOf, connecte
|
|
|
10
10
|
import { DndContext, KeyboardSensor, PointerSensor, TouchSensor, closestCenter, useSensor, useSensors, } from '@dnd-kit/core';
|
|
11
11
|
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable';
|
|
12
12
|
import { CSS } from '@dnd-kit/utilities';
|
|
13
|
-
import { ArrowDownIcon, ArrowLeftIcon, ArrowUpIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, Columns3Icon, CopyIcon, DatabaseIcon, EllipsisIcon, FilterIcon, GripVerticalIcon, LayoutDashboardIcon, Link2Icon, MoveHorizontalIcon, PencilIcon, PlusIcon, ReplaceIcon, RotateCcwIcon, SearchIcon, ShapesIcon, ShrinkIcon, SigmaIcon, SlidersHorizontalIcon, SmileIcon, SparklesIcon, StarIcon, TableIcon, ToggleRightIcon, Trash2Icon, Unlink2Icon, XIcon, } from 'lucide-react';
|
|
13
|
+
import { ArrowDownIcon, ArrowLeftIcon, ArrowUpIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, Columns3Icon, CopyIcon, DatabaseIcon, EllipsisIcon, FilterIcon, GripVerticalIcon, LayoutDashboardIcon, Link2Icon, MoveHorizontalIcon, PencilIcon, PlusIcon, ReplaceIcon, RotateCcwIcon, SearchIcon, ShapesIcon, ShrinkIcon, SigmaIcon, SlidersHorizontalIcon, SmileIcon, SparklesIcon, StarIcon, TableIcon, ToggleRightIcon, Trash2Icon, Unlink2Icon, UnlinkIcon, XIcon, } from 'lucide-react';
|
|
14
14
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
15
15
|
import { AddFormulaDialog } from './add-column.js';
|
|
16
16
|
import { useAssistant, useAssistantName } from './assistant-context.js';
|
|
@@ -808,7 +808,7 @@ function FieldTree({ domain, draft, picked, onPick, why, formulaProblems, onEdit
|
|
|
808
808
|
: isOpen(`g:${g.id}`, all) || (formulasGroup && !all.length);
|
|
809
809
|
const topics = [...new Set(fields.map((f) => f.topic ?? ''))];
|
|
810
810
|
return (_jsxs(Collapsible, { open: open, onOpenChange: () => flip(`g:${g.id}`, open), children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", className: "h-7 min-w-0 flex-1 justify-start gap-2 px-1.5 text-[0.8125rem] font-semibold", children: [_jsx(ChevronRightIcon, { className: cn('text-muted-foreground transition-transform', open && 'rotate-90') }), _jsx("span", { className: "size-2.5 shrink-0 rounded-[3px]", style: { background: color } }), formulasGroup && _jsx(SigmaIcon, { className: "text-muted-foreground" }), linkTo &&
|
|
811
|
-
(unlinked ? (_jsx(
|
|
811
|
+
(unlinked ? (_jsx(UnlinkIcon, { className: "text-destructive" })) : (_jsx(Link2Icon, { className: "text-muted-foreground" }))), recipe && _jsx(SparklesIcon, { className: "text-muted-foreground" }), _jsx("span", { className: "truncate", children: rowsGroup && main ? main.label : g.label }), rowsGroup && (_jsx(Badge, { variant: "secondary", className: "h-4 px-1.5 text-[0.6875rem] font-normal", children: "Rows" })), _jsxs("span", { className: "num ml-auto text-xs font-normal text-muted-foreground", children: [all.filter((f) => picked(f.key)).length, "/", all.length] })] }) }), rowsGroup && main && (_jsxs(_Fragment, { children: [_jsx(LumenSelector, { trigger: "icon", icon: ReplaceIcon, label: "Change the main data", align: "end", search: true, value: null, options: main.options, empty: "No other data", onChange: main.onPick }), _jsx(IconButton, { icon: XIcon, size: "icon-xs", label: `Drop ${main.label}`, onClick: main.onDrop })] })), main && (linkTo || recipe) && (_jsx(IconButton, { icon: StarIcon, size: "icon-xs", label: `Make ${g.label} the main data`, onClick: () => main.onMakeMain(linkTo ?? `${FRAME_LINK}${recipe}`) })), main && linkTo && onUnlink && (_jsx(IconButton, { icon: XIcon, size: "icon-xs", label: `Drop ${g.label}`, onClick: () => onUnlink(linkTo) })), main && recipe && onRemoveRecipe && (_jsx(IconButton, { icon: XIcon, size: "icon-xs", label: `Drop ${g.label}`, onClick: () => onRemoveRecipe(recipe) })), !main && linkTo && onUnlink && (_jsx(LumenConfirm, { title: `Unlink ${g.label}?`, description: "Its columns leave this widget when you save.", action: "Unlink", onConfirm: () => onUnlink(linkTo), children: _jsx(IconButton, { icon: Unlink2Icon, size: "icon-xs", label: `Unlink ${g.label}` }) })), !main && recipe && onRemoveRecipe && (_jsx(LumenConfirm, { title: `Remove ${g.label}?`, description: `Its columns leave this widget when you save. The recipe stays in ${assistantName}.`, action: "Remove", onConfirm: () => onRemoveRecipe(recipe), children: _jsx(IconButton, { icon: XIcon, size: "icon-xs", label: `Remove ${g.label}` }) }))] }), _jsxs(CollapsibleContent, { className: "ml-[0.9rem] flex flex-col gap-px border-l-2 pl-2", style: { borderColor: guide(color) }, children: [g.note && !q && (_jsx("p", { className: "px-1 py-1 text-xs text-muted-foreground", children: g.note })), linkTo && linkPairs && !q && (_jsx(LinkPairs, { label: g.label, on: linkPairs.onOf(linkTo), cardFields: linkPairs.cardFields, fields: linkPairs.fieldsOf(linkTo), onChange: (on) => linkPairs.set(linkTo, on) })), unlinked &&
|
|
812
812
|
linkPairs
|
|
813
813
|
?.fieldsOf(linkTo)
|
|
814
814
|
.filter(match)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@codemirror/lang-javascript": "6.2.5",
|
|
42
42
|
"@codemirror/lang-json": "6.0.2",
|
|
43
43
|
"@codemirror/lang-sql": "6.10.0",
|
|
44
|
-
"@columenlabs/lumen-ui-types": "0.
|
|
44
|
+
"@columenlabs/lumen-ui-types": "0.3.0",
|
|
45
45
|
"@dnd-kit/core": "6.3.1",
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@dnd-kit/utilities": "3.2.2",
|