devnonla-ui 0.1.0 → 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/README.md +22 -10
- package/package.json +2 -2
- package/src/alert/Alert.tsx +1 -1
- package/src/app/App.tsx +38 -18
- package/src/app/context.ts +32 -0
- package/src/button/Button.tsx +2 -2
- package/src/calendar/Calendar.tsx +4 -4
- package/src/chat/AgentPanel.tsx +15 -6
- package/src/chat/common/types.ts +27 -2
- package/src/chat/common/useAgentStream.ts +78 -12
- package/src/chat/common/utils.ts +13 -0
- package/src/chat/index.ts +5 -3
- package/src/chat/message-ui/ChatAgentMessage.tsx +1 -1
- package/src/chat/message-ui/ChatInput.tsx +1 -1
- package/src/chat/message-ui/ChatMarkdown.tsx +6 -6
- package/src/chat/message-ui/ChatMarkdownTable.tsx +1 -1
- package/src/chat/message-ui/ChatThinking.tsx +4 -3
- package/src/chat/message-ui/ChatUserMessage.tsx +0 -8
- package/src/chat/message-ui/MermaidBlock.tsx +11 -1
- package/src/chat/tool-ui/BackgroundTaskToolUI.tsx +2 -1
- package/src/chat/tool-ui/CallAgentToolUI.tsx +3 -2
- package/src/chat/tool-ui/ChatToolCall.tsx +3 -2
- package/src/chat/tool-ui/GetCurrentTimeToolUI.tsx +2 -1
- package/src/chat/tool-ui/ReadSkillToolUI.tsx +3 -2
- package/src/chat/tool-ui/RunJsToolUI.tsx +3 -2
- package/src/chat/tool-ui/WebFetchToolUI.tsx +3 -2
- package/src/chat/tool-ui/registry.ts +24 -12
- package/src/checkbox/Checkbox.tsx +5 -4
- package/src/codeblock/CodeBlock.tsx +1 -0
- package/src/colorpicker/ColorPicker.tsx +694 -30
- package/src/colorpicker/color.ts +246 -0
- package/src/datepicker/DatePicker.tsx +15 -9
- package/src/desktop/DesktopHeader.tsx +1 -1
- package/src/desktop/DesktopIcon.tsx +2 -4
- package/src/desktop/DesktopWindow.tsx +19 -5
- package/src/desktop/MeadowDesktop.tsx +2 -2
- package/src/drawer/Drawer.tsx +8 -6
- package/src/dropdown/ContextMenu.tsx +3 -1
- package/src/dropdown/Dropdown.tsx +8 -6
- package/src/form/Form.tsx +2 -2
- package/src/form/FormItem.tsx +2 -2
- package/src/form/partials/FormItemHelps.tsx +2 -2
- package/src/form/variants/FieldColor.tsx +16 -51
- package/src/form/variants/FieldObject.tsx +2 -2
- package/src/form/variants/FieldRepeaterItem.tsx +4 -4
- package/src/form-layout/FormLayout.tsx +6 -6
- package/src/index.ts +25 -8
- package/src/input/Input.tsx +11 -10
- package/src/lib/sizes.ts +13 -3
- package/src/lib/surface.ts +2 -1
- package/src/menu/Menu.tsx +2 -2
- package/src/message/message.tsx +35 -2
- package/src/modal/Modal.tsx +49 -3
- package/src/pagination/Pagination.tsx +6 -5
- package/src/popconfirm/Popconfirm.tsx +1 -1
- package/src/popover/Popover.tsx +6 -8
- package/src/scroll/OverlayScroll.tsx +5 -3
- package/src/segmented/Segmented.tsx +6 -5
- package/src/select/Select.tsx +9 -6
- package/src/shimmer/Shimmer.tsx +17 -0
- package/src/skeleton/Skeleton.tsx +3 -3
- package/src/spin/Spin.tsx +8 -5
- package/src/splitter/Splitter.tsx +330 -0
- package/src/splitter/sizes.ts +67 -0
- package/src/styles.css +96 -27
- package/src/switch/Switch.tsx +6 -5
- package/src/table/Table.tsx +6 -7
- package/src/tag/Tag.tsx +2 -2
- package/src/theme.ts +23 -3
- package/src/timepicker/TimePicker.tsx +13 -11
- package/src/tooltip/Tooltip.tsx +6 -7
package/src/styles.css
CHANGED
|
@@ -3,26 +3,30 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* NonlaUI theme — single source of color.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Layers:
|
|
7
|
+
* Seed — `--nonla-*` knobs in the first block. Consumer overrides these.
|
|
8
|
+
* Map — derived (brand-50…800, radius-sm/lg, z-popup). Do not set unless exception.
|
|
9
|
+
* Alias — `--background`, `--brand`, `--destructive` (shadcn + Nonla names).
|
|
10
|
+
*
|
|
11
|
+
* Light-only defaults. `.dark` is not an alias of this sheet.
|
|
7
12
|
* Override `--nonla-*` knobs in the consuming app (CSS or `<App theme>`).
|
|
8
13
|
* Components read only these tokens — never edit Button/Tag/… to retheme.
|
|
9
14
|
*/
|
|
10
15
|
|
|
11
16
|
:root,
|
|
12
|
-
.dark,
|
|
13
17
|
.nonla-ui {
|
|
14
18
|
color-scheme: light;
|
|
15
19
|
|
|
16
|
-
/* ── Knobs (override these)
|
|
20
|
+
/* ── Knobs / Seed (override these) ──────────────────────────────────── */
|
|
17
21
|
--nonla-bg: #fffcf1;
|
|
18
22
|
--nonla-fg: #1f1f1e;
|
|
19
23
|
/* Text rank on cream — same ink, contrast steps ~13.6 / 7 / 4.5 / 3 (WCAG AAA body → AA body → AA UI). */
|
|
20
24
|
--nonla-fg-muted: color-mix(in oklab, var(--nonla-fg) 78%, var(--nonla-bg));
|
|
21
25
|
--nonla-fg-tertiary: color-mix(in oklab, var(--nonla-fg) 64%, var(--nonla-bg));
|
|
22
26
|
--nonla-fg-quaternary: color-mix(in oklab, var(--nonla-fg) 50%, var(--nonla-bg));
|
|
23
|
-
--nonla-brand: #
|
|
24
|
-
/*
|
|
25
|
-
--nonla-solid-fg:
|
|
27
|
+
--nonla-brand: #f18d00;
|
|
28
|
+
/* Label on brand fills (Button primary, Tag solid, Checkbox). Override via `colors.solidFg`. */
|
|
29
|
+
--nonla-solid-fg: #ffffff;
|
|
26
30
|
--nonla-danger: #c0392b;
|
|
27
31
|
--nonla-success: #1f9d55;
|
|
28
32
|
--nonla-warn: #c47d12;
|
|
@@ -53,6 +57,11 @@
|
|
|
53
57
|
--nonla-ink-active: color-mix(in srgb, var(--nonla-ink) 8%, transparent);
|
|
54
58
|
--nonla-ink-line: color-mix(in srgb, var(--nonla-ink) 14%, transparent);
|
|
55
59
|
--nonla-desktop-bar-height: 42px;
|
|
60
|
+
--nonla-z-base: 0;
|
|
61
|
+
--nonla-z-desktop: 20;
|
|
62
|
+
--nonla-z-window: 30;
|
|
63
|
+
--nonla-z-header: 40;
|
|
64
|
+
--nonla-z-popup-base: 1000;
|
|
56
65
|
|
|
57
66
|
/* Preset accents — Button color="blue" / Tag color="purple" / … */
|
|
58
67
|
--nonla-blue: #1677ff;
|
|
@@ -69,9 +78,13 @@
|
|
|
69
78
|
--nonla-lime: #a0d911;
|
|
70
79
|
--nonla-gold: #faad14;
|
|
71
80
|
|
|
72
|
-
/* ──
|
|
81
|
+
/* ── Map + Alias (do not set unless you need a one-off exception) ───── */
|
|
73
82
|
--nonla-radius-sm: max(0px, calc(var(--nonla-radius) - 2px));
|
|
74
83
|
--nonla-radius-lg: calc(var(--nonla-radius) + 2px);
|
|
84
|
+
--nonla-z-drawer: var(--nonla-z-popup-base);
|
|
85
|
+
--nonla-z-modal: var(--nonla-z-popup-base);
|
|
86
|
+
--nonla-z-message: calc(var(--nonla-z-popup-base) + 10);
|
|
87
|
+
--nonla-z-popup: calc(var(--nonla-z-popup-base) + 50);
|
|
75
88
|
--radius: var(--nonla-radius);
|
|
76
89
|
--background: var(--nonla-bg);
|
|
77
90
|
--foreground: var(--nonla-fg);
|
|
@@ -106,6 +119,7 @@
|
|
|
106
119
|
--brand-800: color-mix(in oklab, var(--nonla-brand) 48%, black);
|
|
107
120
|
--brand: var(--brand-500);
|
|
108
121
|
--brand-text: var(--brand-700);
|
|
122
|
+
--solid-fg: var(--nonla-solid-fg);
|
|
109
123
|
--link: var(--nonla-link);
|
|
110
124
|
--success: var(--nonla-success);
|
|
111
125
|
--warn: var(--nonla-warn);
|
|
@@ -202,6 +216,7 @@
|
|
|
202
216
|
--color-brand-700: var(--brand-700);
|
|
203
217
|
--color-brand-800: var(--brand-800);
|
|
204
218
|
--color-brand-text: var(--brand-text);
|
|
219
|
+
--color-solid-fg: var(--solid-fg);
|
|
205
220
|
--color-link: var(--link);
|
|
206
221
|
--color-success: var(--success);
|
|
207
222
|
--color-warn: var(--warn);
|
|
@@ -225,13 +240,13 @@
|
|
|
225
240
|
--shadow-panel: 0 0 0 1px var(--border), 0 4px 16px color-mix(in oklab, var(--nonla-fg) 10%, transparent);
|
|
226
241
|
|
|
227
242
|
--spacing-control-xs: 24px;
|
|
228
|
-
--spacing-control-sm:
|
|
243
|
+
--spacing-control-sm: 24px;
|
|
229
244
|
--spacing-control-md: 32px;
|
|
230
|
-
--spacing-control-lg:
|
|
245
|
+
--spacing-control-lg: 40px;
|
|
231
246
|
--spacing-nav-item: 32px;
|
|
232
|
-
--spacing-field-sm:
|
|
247
|
+
--spacing-field-sm: 24px;
|
|
233
248
|
--spacing-field-md: 32px;
|
|
234
|
-
--spacing-field-lg:
|
|
249
|
+
--spacing-field-lg: 40px;
|
|
235
250
|
--spacing-field-nav: 32px;
|
|
236
251
|
--spacing-desktop-bar: var(--nonla-desktop-bar-height);
|
|
237
252
|
|
|
@@ -271,7 +286,7 @@
|
|
|
271
286
|
}
|
|
272
287
|
}
|
|
273
288
|
|
|
274
|
-
@keyframes nonla-
|
|
289
|
+
@keyframes nonla-shimmer {
|
|
275
290
|
0% {
|
|
276
291
|
background-position: 100% 0;
|
|
277
292
|
}
|
|
@@ -280,21 +295,31 @@
|
|
|
280
295
|
}
|
|
281
296
|
}
|
|
282
297
|
|
|
283
|
-
.nonla-
|
|
284
|
-
|
|
285
|
-
background-
|
|
298
|
+
.nonla-shimmer {
|
|
299
|
+
display: inline-block;
|
|
300
|
+
background-image: linear-gradient(
|
|
301
|
+
90deg,
|
|
302
|
+
var(--tertiary-foreground) 0%,
|
|
303
|
+
var(--foreground) 40%,
|
|
304
|
+
color-mix(in oklab, var(--foreground) 25%, white) 50%,
|
|
305
|
+
var(--foreground) 60%,
|
|
306
|
+
var(--tertiary-foreground) 100%
|
|
307
|
+
);
|
|
308
|
+
background-size: 220% 100%;
|
|
286
309
|
background-clip: text;
|
|
287
310
|
-webkit-background-clip: text;
|
|
311
|
+
-webkit-text-fill-color: transparent;
|
|
288
312
|
color: transparent;
|
|
289
|
-
animation: nonla-
|
|
313
|
+
animation: nonla-shimmer 1.6s ease-in-out infinite;
|
|
290
314
|
}
|
|
291
315
|
|
|
292
316
|
@media (prefers-reduced-motion: reduce) {
|
|
293
|
-
.nonla-
|
|
317
|
+
.nonla-shimmer {
|
|
294
318
|
animation: none;
|
|
295
319
|
background-image: none;
|
|
296
320
|
background-clip: border-box;
|
|
297
321
|
-webkit-background-clip: border-box;
|
|
322
|
+
-webkit-text-fill-color: unset;
|
|
298
323
|
color: var(--muted-foreground);
|
|
299
324
|
}
|
|
300
325
|
}
|
|
@@ -305,7 +330,7 @@
|
|
|
305
330
|
top: 16px;
|
|
306
331
|
left: 50%;
|
|
307
332
|
transform: translateX(-50%);
|
|
308
|
-
z-index:
|
|
333
|
+
z-index: var(--nonla-z-message);
|
|
309
334
|
display: flex;
|
|
310
335
|
flex-direction: column;
|
|
311
336
|
align-items: center;
|
|
@@ -589,20 +614,31 @@
|
|
|
589
614
|
}
|
|
590
615
|
}
|
|
591
616
|
|
|
592
|
-
/* Frosted overlay —
|
|
617
|
+
/* Frosted overlay — one hairline + lift. No stacked black/white rings. */
|
|
593
618
|
.nonla-glass {
|
|
594
|
-
border: 1px solid
|
|
619
|
+
border: 1px solid var(--glass-border);
|
|
595
620
|
background: var(--glass);
|
|
596
621
|
color: var(--foreground);
|
|
597
|
-
box-shadow:
|
|
598
|
-
0 0 0 0.5px color-mix(in srgb, black 22%, transparent),
|
|
599
|
-
0 10px 28px color-mix(in srgb, black 14%, transparent),
|
|
600
|
-
inset 0 0 0 0.5px rgb(255 255 255 / 0.55),
|
|
601
|
-
inset 0 1px 0 rgb(255 255 255 / 0.92);
|
|
622
|
+
box-shadow: 0 10px 28px color-mix(in srgb, black 12%, transparent);
|
|
602
623
|
-webkit-backdrop-filter: blur(24px) saturate(160%);
|
|
603
624
|
backdrop-filter: blur(24px) saturate(160%);
|
|
604
625
|
}
|
|
605
626
|
|
|
627
|
+
/* Desktop window — cream wash + less saturate so meadow doesn't tint the panel. */
|
|
628
|
+
.nonla-window-glass {
|
|
629
|
+
background: color-mix(in srgb, var(--nonla-bg) 81%, transparent);
|
|
630
|
+
-webkit-backdrop-filter: blur(40px) saturate(108%);
|
|
631
|
+
backdrop-filter: blur(40px) saturate(108%);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* Fluent Color desktop tile — same frost as the window, macOS squircle. */
|
|
635
|
+
.nonla-desktop-icon-plate {
|
|
636
|
+
overflow: hidden;
|
|
637
|
+
border: 0;
|
|
638
|
+
border-radius: 22%;
|
|
639
|
+
box-shadow: none;
|
|
640
|
+
}
|
|
641
|
+
|
|
606
642
|
.nonla-btn-glass {
|
|
607
643
|
border-color: var(--glass-border);
|
|
608
644
|
background: var(--glass);
|
|
@@ -646,6 +682,7 @@
|
|
|
646
682
|
.nonla-overlay {
|
|
647
683
|
position: fixed;
|
|
648
684
|
inset: 0;
|
|
685
|
+
z-index: var(--nonla-z-drawer);
|
|
649
686
|
background: var(--mask);
|
|
650
687
|
}
|
|
651
688
|
.nonla-overlay[data-state="open"] {
|
|
@@ -658,7 +695,7 @@
|
|
|
658
695
|
.nonla-modal-overlay {
|
|
659
696
|
position: fixed;
|
|
660
697
|
inset: 0;
|
|
661
|
-
z-index:
|
|
698
|
+
z-index: var(--nonla-z-modal);
|
|
662
699
|
background: var(--mask);
|
|
663
700
|
}
|
|
664
701
|
.nonla-modal-overlay[data-state="open"] {
|
|
@@ -673,7 +710,7 @@
|
|
|
673
710
|
position: fixed;
|
|
674
711
|
left: 50%;
|
|
675
712
|
top: 60px;
|
|
676
|
-
z-index:
|
|
713
|
+
z-index: calc(var(--nonla-z-modal) + 1);
|
|
677
714
|
transform: translate(-50%, 0);
|
|
678
715
|
display: flex;
|
|
679
716
|
flex-direction: column;
|
|
@@ -761,6 +798,7 @@
|
|
|
761
798
|
|
|
762
799
|
.nonla-drawer-panel {
|
|
763
800
|
will-change: transform;
|
|
801
|
+
z-index: calc(var(--nonla-z-drawer) + 1);
|
|
764
802
|
}
|
|
765
803
|
.nonla-drawer-panel[data-side="right"][data-state="open"] {
|
|
766
804
|
animation: nonla-drawer-in-right var(--nonla-dur-slow) var(--nonla-ease-out);
|
|
@@ -787,6 +825,22 @@
|
|
|
787
825
|
animation: nonla-drawer-out-top var(--nonla-dur) var(--nonla-ease-in);
|
|
788
826
|
}
|
|
789
827
|
|
|
828
|
+
.nonla-popup-layer {
|
|
829
|
+
z-index: var(--nonla-z-popup, 1050);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.nonla-desktop-layer {
|
|
833
|
+
z-index: var(--nonla-z-desktop, 20);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.nonla-window-layer {
|
|
837
|
+
z-index: var(--nonla-z-window, 30);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.nonla-header-layer {
|
|
841
|
+
z-index: var(--nonla-z-header, 40);
|
|
842
|
+
}
|
|
843
|
+
|
|
790
844
|
.nonla-popper {
|
|
791
845
|
will-change: transform, opacity;
|
|
792
846
|
transform-origin: var(--radix-popover-content-transform-origin, var(--radix-dropdown-menu-content-transform-origin, var(--radix-select-content-transform-origin, var(--radix-tooltip-content-transform-origin, center))));
|
|
@@ -895,3 +949,18 @@
|
|
|
895
949
|
.group\/scroll:hover .nonla-overlay-thumb {
|
|
896
950
|
background: var(--nonla-scrollbar-thumb-hover);
|
|
897
951
|
}
|
|
952
|
+
|
|
953
|
+
.nonla-color-checkered {
|
|
954
|
+
background-color: #fff;
|
|
955
|
+
background-image: conic-gradient(#d9d9d9 0.25turn, #fff 0.25turn 0.5turn, #d9d9d9 0.5turn 0.75turn, #fff 0.75turn);
|
|
956
|
+
background-size: 8px 8px;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.nonla-color-cleared {
|
|
960
|
+
background: linear-gradient(
|
|
961
|
+
to bottom left,
|
|
962
|
+
transparent calc(50% - 1px),
|
|
963
|
+
var(--destructive) calc(50% - 1px) calc(50% + 1px),
|
|
964
|
+
transparent calc(50% + 1px)
|
|
965
|
+
);
|
|
966
|
+
}
|
package/src/switch/Switch.tsx
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
2
2
|
import { type CSSProperties, type ComponentPropsWithoutRef, forwardRef } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
|
-
import { type ControlSize, controlRadiusVar,
|
|
4
|
+
import { type ControlSize, controlRadiusVar, useControlSize } from "../lib/sizes";
|
|
5
5
|
|
|
6
6
|
export type SwitchVariant = "default" | "square";
|
|
7
7
|
|
|
8
8
|
export type SwitchProps = Omit<ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, "onCheckedChange" | "checked" | "onChange"> & {
|
|
9
9
|
checked?: boolean;
|
|
10
10
|
defaultChecked?: boolean;
|
|
11
|
-
/**
|
|
11
|
+
/** Receives the next checked boolean (not a DOM event). */
|
|
12
12
|
onChange?: (checked: boolean) => void;
|
|
13
13
|
size?: ControlSize;
|
|
14
14
|
variant?: SwitchVariant;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
/** Switch track heights — shorter than Button/Input
|
|
17
|
+
/** Switch track heights — shorter than Button/Input. */
|
|
18
18
|
const SWITCH_TRACK_H = { small: 16, default: 22, large: 28 } as const;
|
|
19
19
|
|
|
20
20
|
export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(function Switch({ className, checked, defaultChecked, onChange, size, variant = "default", disabled, style, ...rest }, ref) {
|
|
21
|
-
const
|
|
21
|
+
const resolvedSize = useControlSize(size);
|
|
22
|
+
const trackH = SWITCH_TRACK_H[resolvedSize];
|
|
22
23
|
const trackW = Math.round(trackH * 1.8);
|
|
23
24
|
const thumb = trackH - 4;
|
|
24
25
|
const travel = trackW - thumb - 3;
|
|
25
26
|
const square = variant === "square";
|
|
26
|
-
const squareRadius = `max(3px, calc(${controlRadiusVar(
|
|
27
|
+
const squareRadius = `max(3px, calc(${controlRadiusVar(resolvedSize)} * 0.55))`;
|
|
27
28
|
|
|
28
29
|
return (
|
|
29
30
|
<SwitchPrimitive.Root
|
package/src/table/Table.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { type CSSProperties, type Key, type ReactNode, useMemo, useState } from
|
|
|
2
2
|
import { Checkbox } from "../checkbox/Checkbox";
|
|
3
3
|
import { Empty } from "../empty/Empty";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
|
-
import { type ControlSize,
|
|
5
|
+
import { type CanonicalSize, type ControlSize, useControlSize } from "../lib/sizes";
|
|
6
6
|
import { Pagination } from "../pagination/Pagination";
|
|
7
7
|
import { Spin } from "../spin/Spin";
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ export type ColumnType<T> = {
|
|
|
15
15
|
width?: number | string;
|
|
16
16
|
minWidth?: number | string;
|
|
17
17
|
align?: "left" | "center" | "right";
|
|
18
|
-
/** Custom cell —
|
|
18
|
+
/** Custom cell — `(value, record, index) => ReactNode`. */
|
|
19
19
|
render?: (value: any, record: T, index: number) => ReactNode;
|
|
20
20
|
className?: string;
|
|
21
21
|
ellipsis?: boolean;
|
|
@@ -127,8 +127,7 @@ function SortIcon({ order }: { order: SortOrder | undefined }) {
|
|
|
127
127
|
);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
function sizeClasses(
|
|
131
|
-
const s = normalizeSize(size);
|
|
130
|
+
function sizeClasses(s: CanonicalSize) {
|
|
132
131
|
if (s === "small") {
|
|
133
132
|
return {
|
|
134
133
|
head: "h-9 px-2 text-xs",
|
|
@@ -152,7 +151,7 @@ function sizeClasses(size: ControlSize | undefined) {
|
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
export function Table<T extends object = Record<string, unknown>>({ columns = [], dataSource = [], rowKey, loading, pagination, className, size, bordered, scroll, onRow, locale, showHeader = true, title, footer, rowSelection, rowClassName, onChange }: TableProps<T>) {
|
|
155
|
-
const sz = sizeClasses(size);
|
|
154
|
+
const sz = sizeClasses(useControlSize(size));
|
|
156
155
|
|
|
157
156
|
const selectionControlled = rowSelection?.selectedRowKeys !== undefined;
|
|
158
157
|
const [innerSelected, setInnerSelected] = useState<Key[]>(() => rowSelection?.defaultSelectedRowKeys ?? []);
|
|
@@ -324,8 +323,8 @@ export function Table<T extends object = Record<string, unknown>>({ columns = []
|
|
|
324
323
|
return (
|
|
325
324
|
<tr key={key} data-slot="table-row" data-state={selected ? "selected" : undefined} className={cn("border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", rowProps?.className, extraClass)} style={rowProps?.style} onClick={rowProps?.onClick}>
|
|
326
325
|
{rowSelection ? (
|
|
327
|
-
<td data-slot="table-cell" className={cn(sz.cell, sz.check, "align-middle")} style={selectionColWidth != null ? { width: selectionColWidth } : undefined}>
|
|
328
|
-
<div className="flex items-center justify-center"
|
|
326
|
+
<td data-slot="table-cell" className={cn(sz.cell, sz.check, "align-middle")} style={selectionColWidth != null ? { width: selectionColWidth } : undefined} onClick={(e) => e.stopPropagation()}>
|
|
327
|
+
<div className="flex items-center justify-center">
|
|
329
328
|
{rowSelection.type === "radio" ? (
|
|
330
329
|
<input type="radio" name="nonla-table-row-select" checked={selected} disabled={rowSelection.getCheckboxProps?.(record)?.disabled} aria-label="Select row" className="size-3.5 accent-foreground" onChange={() => emitSelection([key])} />
|
|
331
330
|
) : (
|
package/src/tag/Tag.tsx
CHANGED
|
@@ -6,7 +6,7 @@ export type TagVariant = "soft" | "solid";
|
|
|
6
6
|
export type TagProps = {
|
|
7
7
|
children?: ReactNode;
|
|
8
8
|
color?: string;
|
|
9
|
-
/** @deprecated prefer `variant="soft"`
|
|
9
|
+
/** @deprecated prefer `variant="soft"` */
|
|
10
10
|
bordered?: boolean;
|
|
11
11
|
/**
|
|
12
12
|
* `soft` — quiet wash (default).
|
|
@@ -78,7 +78,7 @@ export function Tag({
|
|
|
78
78
|
return (
|
|
79
79
|
<span
|
|
80
80
|
className={cn(
|
|
81
|
-
"inline-flex h-
|
|
81
|
+
"inline-flex h-5.5 max-w-full items-center gap-1.5 rounded-full px-2.5 text-[11px] font-medium tracking-[0.01em]",
|
|
82
82
|
isDefault && (solid ? "bg-foreground/10 text-(--nonla-ink)" : "bg-muted text-muted-foreground"),
|
|
83
83
|
!isDefault && (solid ? SOLID : SOFT),
|
|
84
84
|
className,
|
package/src/theme.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme knobs for NonlaUI.
|
|
3
3
|
*
|
|
4
|
-
* Defaults live in `styles.css` on `:root` / `.
|
|
4
|
+
* Defaults live in `styles.css` on `:root` / `.nonla-ui`.
|
|
5
5
|
* `--brand-50`…`--brand-800` follow `--nonla-brand` (500 = the knob).
|
|
6
6
|
* Consumers retheme without touching components:
|
|
7
7
|
* 1. CSS: `:root { --nonla-brand: #3b82f6; }`
|
|
@@ -43,6 +43,11 @@ export const NONLA_THEME_KEYS = {
|
|
|
43
43
|
inkActive: "--nonla-ink-active",
|
|
44
44
|
inkLine: "--nonla-ink-line",
|
|
45
45
|
desktopBarHeight: "--nonla-desktop-bar-height",
|
|
46
|
+
zBase: "--nonla-z-base",
|
|
47
|
+
zDesktop: "--nonla-z-desktop",
|
|
48
|
+
zWindow: "--nonla-z-window",
|
|
49
|
+
zHeader: "--nonla-z-header",
|
|
50
|
+
zPopupBase: "--nonla-z-popup-base",
|
|
46
51
|
blue: "--nonla-blue",
|
|
47
52
|
purple: "--nonla-purple",
|
|
48
53
|
cyan: "--nonla-cyan",
|
|
@@ -61,9 +66,12 @@ export const NONLA_THEME_KEYS = {
|
|
|
61
66
|
export type NonlaThemeKnobName = keyof typeof NONLA_THEME_KEYS;
|
|
62
67
|
export type NonlaThemeKnob = (typeof NONLA_THEME_KEYS)[NonlaThemeKnobName];
|
|
63
68
|
|
|
64
|
-
const METRIC_KEYS = new Set<NonlaThemeKnobName>(["radius", "height", "heightSm", "heightLg", "desktopBarHeight"]);
|
|
69
|
+
const METRIC_KEYS = new Set<NonlaThemeKnobName>(["radius", "height", "heightSm", "heightLg", "desktopBarHeight", "zBase", "zDesktop", "zWindow", "zHeader", "zPopupBase"]);
|
|
65
70
|
|
|
66
|
-
export type NonlaThemeColorName = Exclude<
|
|
71
|
+
export type NonlaThemeColorName = Exclude<
|
|
72
|
+
NonlaThemeKnobName,
|
|
73
|
+
"radius" | "height" | "heightSm" | "heightLg" | "desktopBarHeight" | "zBase" | "zDesktop" | "zWindow" | "zHeader" | "zPopupBase"
|
|
74
|
+
>;
|
|
67
75
|
export type NonlaThemeColors = { [K in NonlaThemeColorName]?: string };
|
|
68
76
|
|
|
69
77
|
/** Flat aliases → knob name (`colorBorder` == `colors.border`). */
|
|
@@ -117,6 +125,18 @@ function entriesOf(theme: NonlaThemeConfig): [string, string][] {
|
|
|
117
125
|
return [...map.entries()];
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
export type NonlaTokenSnapshot = { [K in NonlaThemeKnobName]: string };
|
|
129
|
+
|
|
130
|
+
/** Read computed `--nonla-*` knobs (`getDesignToken`). */
|
|
131
|
+
export function getDesignToken(target?: HTMLElement): NonlaTokenSnapshot {
|
|
132
|
+
const el = target ?? (typeof document === "undefined" ? null : document.documentElement);
|
|
133
|
+
const token = {} as NonlaTokenSnapshot;
|
|
134
|
+
for (const name of Object.keys(NONLA_THEME_KEYS) as NonlaThemeKnobName[]) {
|
|
135
|
+
token[name] = el ? getComputedStyle(el).getPropertyValue(NONLA_THEME_KEYS[name]).trim() : "";
|
|
136
|
+
}
|
|
137
|
+
return token;
|
|
138
|
+
}
|
|
139
|
+
|
|
120
140
|
/** Apply knobs on an element (defaults to `:root`). Returns a restore function. */
|
|
121
141
|
export function applyNonlaTheme(theme: NonlaThemeConfig, target: HTMLElement = document.documentElement): () => void {
|
|
122
142
|
const entries = entriesOf(theme);
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
useRef,
|
|
13
13
|
useState,
|
|
14
14
|
} from "react";
|
|
15
|
-
import {
|
|
15
|
+
import { usePopupContainer } from "../app/context";
|
|
16
16
|
import { cn } from "../lib/cn";
|
|
17
17
|
import { type PopperPlacement, placementToRadix } from "../lib/placement";
|
|
18
|
-
import { type ControlSize, controlFieldFocusBorder, controlFieldStyle, controlFieldSurface, controlFieldTransition, controlStatusClass, getSizeTokens } from "../lib/sizes";
|
|
18
|
+
import { type ControlSize, controlFieldFocusBorder, controlFieldStyle, controlFieldSurface, controlFieldTransition, controlStatusClass, getSizeTokens, useControlSize } from "../lib/sizes";
|
|
19
19
|
import { glassOverlayClass } from "../lib/surface";
|
|
20
20
|
|
|
21
21
|
export type TimeValue = { hours: number; minutes: number; seconds: number };
|
|
@@ -244,7 +244,7 @@ function StepperButton({
|
|
|
244
244
|
tabIndex={-1}
|
|
245
245
|
aria-label={label}
|
|
246
246
|
className={cn(
|
|
247
|
-
"flex h-10 items-center justify-center rounded-
|
|
247
|
+
"flex h-10 items-center justify-center rounded-md text-muted-foreground transition-colors",
|
|
248
248
|
"hover:bg-foreground/5 hover:text-foreground active:bg-foreground/8",
|
|
249
249
|
)}
|
|
250
250
|
style={{ width: size }}
|
|
@@ -370,7 +370,7 @@ function StepperBoard({
|
|
|
370
370
|
tabIndex={-1}
|
|
371
371
|
aria-label={`Choose ${col.label}`}
|
|
372
372
|
className={cn(
|
|
373
|
-
"flex cursor-pointer items-center justify-center rounded-
|
|
373
|
+
"flex cursor-pointer items-center justify-center rounded-md font-semibold leading-none tracking-tight transition-colors",
|
|
374
374
|
col.wide ? "text-[23px]" : "tabular-nums",
|
|
375
375
|
col.active ? "bg-foreground/6 text-foreground" : "text-foreground hover:bg-foreground/5",
|
|
376
376
|
)}
|
|
@@ -414,7 +414,7 @@ export const TimePicker = forwardRef<HTMLInputElement, TimePickerProps>(function
|
|
|
414
414
|
},
|
|
415
415
|
ref,
|
|
416
416
|
) {
|
|
417
|
-
const
|
|
417
|
+
const appPortal = usePopupContainer();
|
|
418
418
|
const showSeconds = format === "HH:mm:ss";
|
|
419
419
|
const controlled = value !== undefined;
|
|
420
420
|
const [inner, setInner] = useState<TimeValue | null>(() => parseTime(defaultValue));
|
|
@@ -428,8 +428,9 @@ export const TimePicker = forwardRef<HTMLInputElement, TimePickerProps>(function
|
|
|
428
428
|
const [fallback, setFallback] = useState<TimeValue>(nowParts);
|
|
429
429
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
430
430
|
const fieldRef = useRef<HTMLDivElement | null>(null);
|
|
431
|
-
const
|
|
432
|
-
const
|
|
431
|
+
const resolvedSize = useControlSize(size);
|
|
432
|
+
const tok = getSizeTokens(resolvedSize);
|
|
433
|
+
const fieldStyle = controlFieldStyle(resolvedSize);
|
|
433
434
|
const { side, align } = placementToRadix(placement);
|
|
434
435
|
|
|
435
436
|
useEffect(() => {
|
|
@@ -593,6 +594,7 @@ export const TimePicker = forwardRef<HTMLInputElement, TimePickerProps>(function
|
|
|
593
594
|
<span className="inline-flex shrink-0 text-muted-foreground">
|
|
594
595
|
<ClockIcon size={tok.icon} />
|
|
595
596
|
</span>
|
|
597
|
+
{/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: native time field exposes popup state */}
|
|
596
598
|
<input
|
|
597
599
|
ref={setRefs}
|
|
598
600
|
id={id}
|
|
@@ -647,7 +649,7 @@ export const TimePicker = forwardRef<HTMLInputElement, TimePickerProps>(function
|
|
|
647
649
|
) : null}
|
|
648
650
|
</div>
|
|
649
651
|
</PopoverPrimitive.Anchor>
|
|
650
|
-
<PopoverPrimitive.Portal container={
|
|
652
|
+
<PopoverPrimitive.Portal container={appPortal?.()}>
|
|
651
653
|
<PopoverPrimitive.Content
|
|
652
654
|
side={side}
|
|
653
655
|
align={align}
|
|
@@ -668,14 +670,14 @@ export const TimePicker = forwardRef<HTMLInputElement, TimePickerProps>(function
|
|
|
668
670
|
if (fieldRef.current?.contains(e.target as Node)) e.preventDefault();
|
|
669
671
|
}}
|
|
670
672
|
className={cn(
|
|
671
|
-
"w-
|
|
673
|
+
"w-75 p-0",
|
|
672
674
|
glassOverlayClass,
|
|
673
675
|
popupClassName,
|
|
674
676
|
)}
|
|
675
677
|
>
|
|
676
678
|
<div className="relative overflow-hidden rounded-[inherit]">
|
|
677
679
|
{picking && pickOptions ? (
|
|
678
|
-
<div className="relative flex h-
|
|
680
|
+
<div className="relative flex h-55 items-center px-3 pt-8 pb-3">
|
|
679
681
|
<button
|
|
680
682
|
type="button"
|
|
681
683
|
tabIndex={-1}
|
|
@@ -690,7 +692,7 @@ export const TimePicker = forwardRef<HTMLInputElement, TimePickerProps>(function
|
|
|
690
692
|
</div>
|
|
691
693
|
</div>
|
|
692
694
|
) : (
|
|
693
|
-
<div className="flex h-
|
|
695
|
+
<div className="flex h-55 flex-col items-center justify-center px-4">
|
|
694
696
|
<StepperBoard
|
|
695
697
|
columns={[
|
|
696
698
|
{
|
package/src/tooltip/Tooltip.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { usePopupContainer } from "../app/context";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { type PopperPlacement, placementToRadix } from "../lib/placement";
|
|
6
6
|
import { glassOverlayClass } from "../lib/surface";
|
|
@@ -16,10 +16,10 @@ export type TooltipProps = {
|
|
|
16
16
|
onOpenChange?: (open: boolean) => void;
|
|
17
17
|
mouseEnterDelay?: number;
|
|
18
18
|
className?: string;
|
|
19
|
-
/**
|
|
19
|
+
/** Extra class on the overlay panel. */
|
|
20
20
|
overlayClassName?: string;
|
|
21
21
|
getPopupContainer?: () => HTMLElement;
|
|
22
|
-
/**
|
|
22
|
+
/** `boolean | { pointAtCenter }` — we only honor truthiness. */
|
|
23
23
|
arrow?: boolean | { pointAtCenter?: boolean };
|
|
24
24
|
};
|
|
25
25
|
|
|
@@ -35,15 +35,14 @@ export function Tooltip({
|
|
|
35
35
|
overlayClassName,
|
|
36
36
|
getPopupContainer,
|
|
37
37
|
}: TooltipProps) {
|
|
38
|
-
const
|
|
38
|
+
const getContainer = usePopupContainer(getPopupContainer);
|
|
39
39
|
const { side, align } = placementToRadix(placement);
|
|
40
|
-
const container = getPopupContainer ?? app.getPopupContainer;
|
|
41
40
|
if (title == null || title === false) return <>{children}</>;
|
|
42
41
|
return (
|
|
43
42
|
<TooltipPrimitive.Provider delayDuration={Math.round(mouseEnterDelay * 1000)}>
|
|
44
43
|
<TooltipPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
45
44
|
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
|
46
|
-
<TooltipPrimitive.Portal container={
|
|
45
|
+
<TooltipPrimitive.Portal container={getContainer()}>
|
|
47
46
|
<TooltipPrimitive.Content
|
|
48
47
|
side={side}
|
|
49
48
|
align={align}
|