@columenlabs/lumen-ui 0.1.172 → 0.1.173
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lumen-view.js +21 -3
- package/dist/tag-chips.d.ts +22 -0
- package/dist/tag-chips.js +96 -0
- package/dist/view-model.d.ts +7 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { LumenLayout, type LumenLayoutCard, type LumenLayoutProps, type LumenLay
|
|
|
3
3
|
export { LumenViewCard, type LumenViewCardProps } from './lumen-view-card.js';
|
|
4
4
|
export { LumenSelector, LumenSelectorList, type LumenSelectorListHandle, type LumenSelectorProps, type LumenOption, type LumenOptionGroup, SelectorRowContent, } from './lumen-selector.js';
|
|
5
5
|
export { ColorChip, ColorDot, colorIcon } from './color-dot.js';
|
|
6
|
+
export { TagChips, tagColor, tagIcon } from './tag-chips.js';
|
|
6
7
|
export { EditCell, EditInputCell } from './view-edit-cell.js';
|
|
7
8
|
export { type CardOnScreen, cardsOnScreen, kindName, onScreen, pointAt, pointAtCard, type PointedAt, type SectionOnScreen, sectionsOnScreen, spotlight, targetEl, textOf, } from './spotlight.js';
|
|
8
9
|
export { LinkDot, LinkPicker, type LinkColor, linkVar } from './lumen-links.js';
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { LumenLayout, domainKeys, followRows, } from './lumen-layout.js';
|
|
|
10
10
|
export { LumenViewCard } from './lumen-view-card.js';
|
|
11
11
|
export { LumenSelector, LumenSelectorList, SelectorRowContent, } from './lumen-selector.js';
|
|
12
12
|
export { ColorChip, ColorDot, colorIcon } from './color-dot.js';
|
|
13
|
+
export { TagChips, tagColor, tagIcon } from './tag-chips.js';
|
|
13
14
|
export { EditCell, EditInputCell } from './view-edit-cell.js';
|
|
14
15
|
export { cardsOnScreen, kindName, onScreen, pointAt, pointAtCard, sectionsOnScreen, spotlight, targetEl, textOf, } from './spotlight.js';
|
|
15
16
|
export { LinkDot, LinkPicker, linkVar } from './lumen-links.js';
|
package/dist/lumen-view.js
CHANGED
|
@@ -29,6 +29,7 @@ import { MAX_COMPARE, useSymbolChart } from './market/lumen-chart.js';
|
|
|
29
29
|
import { LumenPageFooter, LumenPager } from './pager.js';
|
|
30
30
|
import { maskAmount, useAmountsHidden } from './shell.js';
|
|
31
31
|
import { useSymbolSheet } from './symbol-sheet-context.js';
|
|
32
|
+
import { TagChips, tagColor, tagIcon } from './tag-chips.js';
|
|
32
33
|
import { Badge } from './ui/badge.js';
|
|
33
34
|
import { Button } from './ui/button.js';
|
|
34
35
|
import { Checkbox } from './ui/checkbox.js';
|
|
@@ -66,6 +67,17 @@ export function mergeRows(domain, rows, groupBy) {
|
|
|
66
67
|
const MINUS = '−';
|
|
67
68
|
/** Text cells longer than this many characters are cut to one line with a tooltip. */
|
|
68
69
|
const LONG_TEXT = 48;
|
|
70
|
+
/** A tags field's tags for a row: its `values`, else its text split on commas. */
|
|
71
|
+
function tagsOf(f, row, v) {
|
|
72
|
+
if (f.values)
|
|
73
|
+
return f.values(row);
|
|
74
|
+
return typeof v === 'string'
|
|
75
|
+
? v
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((t) => t.trim())
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
: [];
|
|
80
|
+
}
|
|
69
81
|
/** Rows of a view the assistant sees with each message. */
|
|
70
82
|
const ASSISTANT_ROWS = 10;
|
|
71
83
|
const formatter = (digits) => new Intl.NumberFormat('en-US', { minimumFractionDigits: digits, maximumFractionDigits: digits });
|
|
@@ -555,12 +567,16 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
555
567
|
const valueCellOf = (f, row, chip, v) => {
|
|
556
568
|
if (f.render)
|
|
557
569
|
return f.render(row);
|
|
570
|
+
if (f.tags && !f.edit?.many)
|
|
571
|
+
return (_jsx(TagChips, { tags: tagsOf(f, row, v), colorOf: f.color, iconOf: f.iconOf, empty: f.empty }));
|
|
558
572
|
if (!f.edit)
|
|
559
573
|
return (_jsx(ViewValueCell, { value: v, format: f.format, chip: chip, tone: chip ? f.tone?.(row) : undefined, color: typeof v === 'string' ? f.color?.(v) : undefined, amount: f.amount, compact: f.compact, empty: f.empty }));
|
|
560
574
|
const edit = f.edit;
|
|
561
575
|
const many = edit.many;
|
|
562
576
|
if (many)
|
|
563
|
-
return (_jsx(EditManyCell, { label: f.label, values: f.
|
|
577
|
+
return (_jsx(EditManyCell, { label: f.label, values: f.tags
|
|
578
|
+
? tagsOf(f, row, v)
|
|
579
|
+
: (f.values?.(row) ?? (typeof v === 'string' && v !== '' ? [v] : [])), options: options.get(f.key)?.inUse ?? [], unused: options.get(f.key)?.unused, note: edit.note?.(row), colorOf: f.tags ? (t) => f.color?.(t) ?? tagColor(t) : f.color, iconOf: f.tags ? (t) => f.iconOf?.(t) ?? tagIcon(t) : f.iconOf, onSave: (values) => many(row, values) }));
|
|
564
580
|
const save = edit.save;
|
|
565
581
|
if (!save)
|
|
566
582
|
return _jsx(ViewValueCell, { value: v, format: f.format, chip: chip });
|
|
@@ -895,8 +911,10 @@ function PivotTable({ domain, rows, pivot, }) {
|
|
|
895
911
|
/** A group heading's mark: its value's icon in its colour, else its colour's dot, else the
|
|
896
912
|
* field's icon. */
|
|
897
913
|
function GroupMark({ field, value }) {
|
|
898
|
-
|
|
899
|
-
const
|
|
914
|
+
// A tags field's one tag takes its own colour and icon, as its chips do.
|
|
915
|
+
const tag = field.tags && !value.includes(',');
|
|
916
|
+
const color = field.color?.(value) ?? (tag ? tagColor(value) : undefined);
|
|
917
|
+
const Icon = field.iconOf?.(value) ?? (tag ? tagIcon(value) : undefined);
|
|
900
918
|
if (Icon)
|
|
901
919
|
return (_jsx("span", { className: "inline-flex", style: color ? { color } : undefined, children: _jsx(Icon, { className: "size-3.5" }) }));
|
|
902
920
|
if (color)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
type Icon = ComponentType<{
|
|
3
|
+
className?: string;
|
|
4
|
+
}>;
|
|
5
|
+
/** A tag's colour, picked from its name: the same tag is the same colour everywhere. */
|
|
6
|
+
export declare function tagColor(tag: string): string;
|
|
7
|
+
/** A tag's icon from its words (`cybersecurity` a shield, `ai` sparkles), or none when no word fits. */
|
|
8
|
+
export declare function tagIcon(tag: string): Icon | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* A row's tags as chips on one line: each in its colour with its icon. Past `max` the rest fold
|
|
11
|
+
* into a `+N` chip whose hover names them all, so a table row never wraps. `colorOf` and `iconOf`
|
|
12
|
+
* override the name-picked colour and icon (an app's own label colours).
|
|
13
|
+
*/
|
|
14
|
+
export declare function TagChips({ tags, max, colorOf, iconOf, empty, }: {
|
|
15
|
+
tags: readonly string[];
|
|
16
|
+
max?: number;
|
|
17
|
+
colorOf?: (tag: string) => string | undefined;
|
|
18
|
+
iconOf?: (tag: string) => Icon | undefined;
|
|
19
|
+
/** What no tags says; left out: a dash. */
|
|
20
|
+
empty?: string;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Tags (a row's several free-form labels: `ai`, `cybersecurity`, `favorite`) as coloured chips,
|
|
3
|
+
// each with an icon where its words suggest one. The colour comes from the tag's name, so one tag
|
|
4
|
+
// reads the same colour on every row, page and app with nothing stored.
|
|
5
|
+
import { Baby, BookOpen, Bot, Briefcase, Bug, Building2, Car, Check, Clock, Cloud, Code, Cpu, Database, DollarSign, Dumbbell, Factory, Film, Flag, Flame, Gamepad2, Globe, GraduationCap, Heart, HeartPulse, Hourglass, House, Landmark, Leaf, Lock, MapPin, Megaphone, Music, Network, Palette, Plane, Rocket, Server, Shield, ShoppingCart, Smartphone, Sparkles, Star, TriangleAlert, Truck, Users, Utensils, Wrench, Zap, } from 'lucide-react';
|
|
6
|
+
import { ColorChip } from './color-dot.js';
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
|
|
8
|
+
// Chart colours that read as a hue in light and dark (not the grey `--chart-8`).
|
|
9
|
+
const TAG_COLORS = [1, 2, 3, 4, 5, 6, 7, 9, 10].map((n) => `var(--chart-${n})`);
|
|
10
|
+
/** A tag's colour, picked from its name: the same tag is the same colour everywhere. */
|
|
11
|
+
export function tagColor(tag) {
|
|
12
|
+
const key = tag.trim().toLowerCase();
|
|
13
|
+
let h = 2166136261;
|
|
14
|
+
for (let i = 0; i < key.length; i++)
|
|
15
|
+
h = Math.imul(h ^ key.charCodeAt(i), 16777619);
|
|
16
|
+
return TAG_COLORS[(h >>> 0) % TAG_COLORS.length];
|
|
17
|
+
}
|
|
18
|
+
// Words to icons, most specific first: a tag takes the icon of the first entry any of its words
|
|
19
|
+
// matches (`data-security` is a shield, `ai-buildout` a server).
|
|
20
|
+
const TAG_ICONS = [
|
|
21
|
+
[/^(fav|favou?rites?|starred|top|pick)$/, Star],
|
|
22
|
+
[/^(cyber|cybersecurity|security|secure|infosec|appsec|privacy)$/, Shield],
|
|
23
|
+
[/^(auth|identity|iam|zero|zerotrust|crypto|encryption)$/, Lock],
|
|
24
|
+
[/^(buildout|datacenters?|dc|infra|infrastructure|servers?|hosting|compute)$/, Server],
|
|
25
|
+
[/^(chips?|semis?|semiconductors?|hardware|gpus?|silicon)$/, Cpu],
|
|
26
|
+
[/^(ai|ml|llm|genai|agents?|agentic)$/, Sparkles],
|
|
27
|
+
[/^(bots?|robots?|robotics|automation)$/, Bot],
|
|
28
|
+
[/^(data|database|db|analytics|warehouse|sql|etl)$/, Database],
|
|
29
|
+
[/^(cloud|saas|serverless)$/, Cloud],
|
|
30
|
+
[/^(network|networking|edge|cdn)$/, Network],
|
|
31
|
+
[/^(bigtech|faang|enterprise|corp|corporate|company)$/, Building2],
|
|
32
|
+
[/^(dev|devtools|developer|developers|code|coding|engineering|software|platform)$/, Code],
|
|
33
|
+
[/^(startup|startups|growth|launch)$/, Rocket],
|
|
34
|
+
[/^(fintech|finance|financial|bank|banking|money|pay|payments|budget)$/, DollarSign],
|
|
35
|
+
[/^(gov|government|public|federal|civic)$/, Landmark],
|
|
36
|
+
[/^(health|healthcare|medical|bio|biotech|pharma)$/, HeartPulse],
|
|
37
|
+
[/^(fitness|gym|workout|sports?)$/, Dumbbell],
|
|
38
|
+
[/^(energy|power|electric|ev|battery|utilities)$/, Zap],
|
|
39
|
+
[/^(climate|green|sustainability|eco|garden|plants?)$/, Leaf],
|
|
40
|
+
[/^(manufacturing|industrial|factory)$/, Factory],
|
|
41
|
+
[/^(mobile|phone|ios|android|apps?)$/, Smartphone],
|
|
42
|
+
[/^(remote|global|international|web|online)$/, Globe],
|
|
43
|
+
[/^(local|onsite|hybrid|office|nearby)$/, MapPin],
|
|
44
|
+
[/^(home|house|family)$/, House],
|
|
45
|
+
[/^(kids?|baby|children)$/, Baby],
|
|
46
|
+
[/^(team|people|social|community|hr)$/, Users],
|
|
47
|
+
[/^(jobs?|work|career|hiring)$/, Briefcase],
|
|
48
|
+
[/^(edu|education|learning|school|course|courses|training)$/, GraduationCap],
|
|
49
|
+
[/^(books?|reading|docs|research)$/, BookOpen],
|
|
50
|
+
[/^(games?|gaming)$/, Gamepad2],
|
|
51
|
+
[/^(music|audio)$/, Music],
|
|
52
|
+
[/^(film|video|movies?|media|streaming)$/, Film],
|
|
53
|
+
[/^(design|art|creative)$/, Palette],
|
|
54
|
+
[/^(marketing|ads|sales|pr)$/, Megaphone],
|
|
55
|
+
[/^(shop|shopping|retail|ecommerce|commerce)$/, ShoppingCart],
|
|
56
|
+
[/^(food|recipes?|dinner|lunch|breakfast|dessert|cooking)$/, Utensils],
|
|
57
|
+
[/^(travel|trip|flights?)$/, Plane],
|
|
58
|
+
[/^(cars?|auto|automotive)$/, Car],
|
|
59
|
+
[/^(logistics|shipping|delivery|supply)$/, Truck],
|
|
60
|
+
[/^(tools?|repair|maintenance|fix)$/, Wrench],
|
|
61
|
+
[/^(bugs?|issues?)$/, Bug],
|
|
62
|
+
[/^(urgent|hot|asap)$/, Flame],
|
|
63
|
+
[/^(risk|warning|alert|blocked)$/, TriangleAlert],
|
|
64
|
+
[/^(done|ok|approved|verified)$/, Check],
|
|
65
|
+
[/^(waiting|pending|later|someday)$/, Hourglass],
|
|
66
|
+
[/^(daily|weekly|monthly|recurring|time)$/, Clock],
|
|
67
|
+
[/^(goal|goals|milestone|priority)$/, Flag],
|
|
68
|
+
[/^(love|loved|liked)$/, Heart],
|
|
69
|
+
];
|
|
70
|
+
const iconCache = new Map();
|
|
71
|
+
/** A tag's icon from its words (`cybersecurity` a shield, `ai` sparkles), or none when no word fits. */
|
|
72
|
+
export function tagIcon(tag) {
|
|
73
|
+
const key = tag.trim().toLowerCase();
|
|
74
|
+
let icon = iconCache.get(key);
|
|
75
|
+
if (icon === undefined) {
|
|
76
|
+
const words = key.split(/[^a-z0-9]+/).filter(Boolean);
|
|
77
|
+
icon = TAG_ICONS.find(([re]) => words.some((w) => re.test(w)))?.[1] ?? null;
|
|
78
|
+
iconCache.set(key, icon);
|
|
79
|
+
}
|
|
80
|
+
return icon ?? undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A row's tags as chips on one line: each in its colour with its icon. Past `max` the rest fold
|
|
84
|
+
* into a `+N` chip whose hover names them all, so a table row never wraps. `colorOf` and `iconOf`
|
|
85
|
+
* override the name-picked colour and icon (an app's own label colours).
|
|
86
|
+
*/
|
|
87
|
+
export function TagChips({ tags, max = 3, colorOf, iconOf, empty, }) {
|
|
88
|
+
if (!tags.length)
|
|
89
|
+
return _jsx("span", { className: "text-muted-foreground", children: empty ?? '—' });
|
|
90
|
+
const shown = tags.slice(0, max);
|
|
91
|
+
const rest = tags.length - shown.length;
|
|
92
|
+
const chips = (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1 overflow-hidden", children: [shown.map((t) => (_jsx(ColorChip, { color: colorOf?.(t) ?? tagColor(t), icon: iconOf?.(t) ?? tagIcon(t), children: t }, t))), rest > 0 && _jsxs(ColorChip, { children: ["+", rest] })] }));
|
|
93
|
+
if (!rest)
|
|
94
|
+
return chips;
|
|
95
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: chips }), _jsx(TooltipContent, { className: "max-w-md whitespace-normal", children: tags.join(', ') })] }));
|
|
96
|
+
}
|
package/dist/view-model.d.ts
CHANGED
|
@@ -25,6 +25,13 @@ export interface ViewField<T> {
|
|
|
25
25
|
topic?: string;
|
|
26
26
|
/** Every value a row has for filtering, when it has several (a symbol in Holdings and Watchlist). */
|
|
27
27
|
values?: (row: T) => string[];
|
|
28
|
+
/**
|
|
29
|
+
* The values are tags (a row's several free-form labels): the cell draws each of `values` as a
|
|
30
|
+
* chip in its colour with its icon (`TagChips`), never a comma list. The colour and icon come
|
|
31
|
+
* from the tag's name unless `color` or `iconOf` gives them. Without `values` the text value is
|
|
32
|
+
* split on commas.
|
|
33
|
+
*/
|
|
34
|
+
tags?: boolean;
|
|
28
35
|
format: ViewFormat;
|
|
29
36
|
/** How the value is made, shown under the field in the Edit dialog. */
|
|
30
37
|
formula?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.173",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@codemirror/lang-javascript": "6.2.5",
|
|
50
50
|
"@codemirror/lang-json": "6.0.2",
|
|
51
51
|
"@codemirror/lang-sql": "6.10.0",
|
|
52
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.173",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|