devnonla-ui 0.1.0 → 0.2.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.
Files changed (67) hide show
  1. package/README.md +22 -10
  2. package/package.json +2 -2
  3. package/src/alert/Alert.tsx +1 -1
  4. package/src/app/App.tsx +38 -18
  5. package/src/app/context.ts +32 -0
  6. package/src/button/Button.tsx +2 -2
  7. package/src/calendar/Calendar.tsx +4 -4
  8. package/src/chat/AgentPanel.tsx +15 -6
  9. package/src/chat/common/types.ts +27 -2
  10. package/src/chat/common/useAgentStream.ts +78 -12
  11. package/src/chat/common/utils.ts +13 -0
  12. package/src/chat/index.ts +5 -3
  13. package/src/chat/message-ui/ChatAgentMessage.tsx +1 -1
  14. package/src/chat/message-ui/ChatInput.tsx +1 -1
  15. package/src/chat/message-ui/ChatMarkdown.tsx +6 -6
  16. package/src/chat/message-ui/ChatMarkdownTable.tsx +1 -1
  17. package/src/chat/message-ui/ChatThinking.tsx +4 -3
  18. package/src/chat/message-ui/ChatUserMessage.tsx +0 -8
  19. package/src/chat/message-ui/MermaidBlock.tsx +11 -1
  20. package/src/chat/tool-ui/BackgroundTaskToolUI.tsx +2 -1
  21. package/src/chat/tool-ui/CallAgentToolUI.tsx +3 -2
  22. package/src/chat/tool-ui/ChatToolCall.tsx +3 -2
  23. package/src/chat/tool-ui/GetCurrentTimeToolUI.tsx +2 -1
  24. package/src/chat/tool-ui/ReadSkillToolUI.tsx +3 -2
  25. package/src/chat/tool-ui/RunJsToolUI.tsx +3 -2
  26. package/src/chat/tool-ui/WebFetchToolUI.tsx +3 -2
  27. package/src/chat/tool-ui/registry.ts +24 -12
  28. package/src/checkbox/Checkbox.tsx +5 -4
  29. package/src/codeblock/CodeBlock.tsx +1 -0
  30. package/src/datepicker/DatePicker.tsx +15 -9
  31. package/src/desktop/DesktopHeader.tsx +1 -1
  32. package/src/desktop/DesktopIcon.tsx +2 -4
  33. package/src/desktop/DesktopWindow.tsx +19 -5
  34. package/src/desktop/MeadowDesktop.tsx +2 -2
  35. package/src/drawer/Drawer.tsx +8 -6
  36. package/src/dropdown/ContextMenu.tsx +3 -1
  37. package/src/dropdown/Dropdown.tsx +8 -6
  38. package/src/form/Form.tsx +2 -2
  39. package/src/form/FormItem.tsx +2 -2
  40. package/src/form/partials/FormItemHelps.tsx +2 -2
  41. package/src/form/variants/FieldColor.tsx +1 -1
  42. package/src/form/variants/FieldObject.tsx +2 -2
  43. package/src/form/variants/FieldRepeaterItem.tsx +4 -4
  44. package/src/form-layout/FormLayout.tsx +6 -6
  45. package/src/index.ts +20 -6
  46. package/src/input/Input.tsx +11 -10
  47. package/src/lib/sizes.ts +13 -3
  48. package/src/lib/surface.ts +2 -1
  49. package/src/menu/Menu.tsx +2 -2
  50. package/src/message/message.tsx +35 -2
  51. package/src/modal/Modal.tsx +49 -3
  52. package/src/pagination/Pagination.tsx +6 -5
  53. package/src/popconfirm/Popconfirm.tsx +1 -1
  54. package/src/popover/Popover.tsx +6 -8
  55. package/src/scroll/OverlayScroll.tsx +5 -3
  56. package/src/segmented/Segmented.tsx +6 -5
  57. package/src/select/Select.tsx +9 -6
  58. package/src/shimmer/Shimmer.tsx +17 -0
  59. package/src/skeleton/Skeleton.tsx +3 -3
  60. package/src/spin/Spin.tsx +8 -5
  61. package/src/styles.css +81 -27
  62. package/src/switch/Switch.tsx +6 -5
  63. package/src/table/Table.tsx +6 -7
  64. package/src/tag/Tag.tsx +2 -2
  65. package/src/theme.ts +23 -3
  66. package/src/timepicker/TimePicker.tsx +13 -11
  67. package/src/tooltip/Tooltip.tsx +6 -7
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` / `.dark` / `.nonla-ui`.
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<NonlaThemeKnobName, "radius" | "height" | "heightSm" | "heightLg">;
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 { useAppConfig } from "../app/App";
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-[6px] text-muted-foreground transition-colors",
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-[6px] font-semibold leading-none tracking-tight transition-colors",
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 app = useAppConfig();
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 tok = getSizeTokens(size);
432
- const fieldStyle = controlFieldStyle(size);
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={app.getPopupContainer?.()}>
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-[300px] p-0",
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-[220px] items-center px-3 pt-8 pb-3">
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-[220px] flex-col items-center justify-center px-4">
695
+ <div className="flex h-55 flex-col items-center justify-center px-4">
694
696
  <StepperBoard
695
697
  columns={[
696
698
  {
@@ -1,6 +1,6 @@
1
1
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2
- import { type ReactNode } from "react";
3
- import { useAppConfig } from "../app/App";
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
- /** antd alias */
19
+ /** Extra class on the overlay panel. */
20
20
  overlayClassName?: string;
21
21
  getPopupContainer?: () => HTMLElement;
22
- /** antd accepts `boolean | { pointAtCenter }` — we only honor truthiness. */
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 app = useAppConfig();
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={container?.()}>
45
+ <TooltipPrimitive.Portal container={getContainer()}>
47
46
  <TooltipPrimitive.Content
48
47
  side={side}
49
48
  align={align}