@kitnai/chat 0.3.0 → 0.4.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 (106) hide show
  1. package/README.md +11 -0
  2. package/dist/custom-elements.json +2494 -0
  3. package/dist/kitn-chat.es.js +52 -39
  4. package/dist/llms/llms-full.txt +667 -0
  5. package/dist/llms/llms.txt +104 -0
  6. package/dist/theme.tokens.css +133 -0
  7. package/frameworks/react/index.tsx +530 -0
  8. package/frameworks/react/runtime.tsx +94 -0
  9. package/llms-full.txt +667 -0
  10. package/llms.txt +104 -0
  11. package/package.json +34 -5
  12. package/src/components/attachments.tsx +4 -2
  13. package/src/components/chain-of-thought.tsx +1 -1
  14. package/src/components/chat-scope-picker.tsx +2 -2
  15. package/src/components/checkpoint.tsx +7 -3
  16. package/src/components/context.tsx +14 -18
  17. package/src/components/conversation-item.tsx +1 -1
  18. package/src/components/conversation-list.tsx +5 -4
  19. package/src/components/message-skills.tsx +1 -1
  20. package/src/components/message.tsx +1 -0
  21. package/src/components/model-switcher.tsx +3 -3
  22. package/src/components/prompt-input.tsx +15 -2
  23. package/src/components/reasoning.tsx +2 -2
  24. package/src/components/scroll-button.tsx +1 -0
  25. package/src/components/slash-command.tsx +17 -8
  26. package/src/components/source.tsx +2 -2
  27. package/src/components/thinking-bar.tsx +2 -2
  28. package/src/components/tool.tsx +17 -6
  29. package/src/components/voice-input.tsx +5 -1
  30. package/src/elements/attachments.tsx +132 -0
  31. package/src/elements/chain-of-thought.tsx +45 -0
  32. package/src/elements/chat-scope-picker.tsx +36 -0
  33. package/src/elements/chat.tsx +51 -7
  34. package/src/elements/checkpoint.tsx +43 -0
  35. package/src/elements/code-block.tsx +42 -0
  36. package/src/elements/compiled.css +1 -1
  37. package/src/elements/context-meter.tsx +71 -0
  38. package/src/elements/conversation-list.tsx +6 -0
  39. package/src/elements/default-input.tsx +22 -1
  40. package/src/elements/define.tsx +102 -13
  41. package/src/elements/element-types.d.ts +404 -0
  42. package/src/elements/empty.tsx +29 -0
  43. package/src/elements/feedback-bar.tsx +33 -0
  44. package/src/elements/file-upload.tsx +44 -0
  45. package/src/elements/image.tsx +32 -0
  46. package/src/elements/kitn-attachments.stories.tsx +181 -0
  47. package/src/elements/kitn-chain-of-thought.stories.tsx +75 -0
  48. package/src/elements/kitn-chat-scope-picker.stories.tsx +72 -0
  49. package/src/elements/kitn-checkpoint.stories.tsx +71 -0
  50. package/src/elements/kitn-code-block.stories.tsx +82 -0
  51. package/src/elements/kitn-context-meter.stories.tsx +85 -0
  52. package/src/elements/kitn-empty.stories.tsx +110 -0
  53. package/src/elements/kitn-feedback-bar.stories.tsx +73 -0
  54. package/src/elements/kitn-file-upload.stories.tsx +81 -0
  55. package/src/elements/kitn-image.stories.tsx +70 -0
  56. package/src/elements/kitn-loader.stories.tsx +87 -0
  57. package/src/elements/kitn-markdown.stories.tsx +75 -0
  58. package/src/elements/kitn-message-skills.stories.tsx +74 -0
  59. package/src/elements/kitn-message.stories.tsx +105 -0
  60. package/src/elements/kitn-model-switcher.stories.tsx +80 -0
  61. package/src/elements/kitn-prompt-input.stories.tsx +74 -16
  62. package/src/elements/kitn-prompt-suggestions.stories.tsx +157 -0
  63. package/src/elements/kitn-reasoning.stories.tsx +76 -0
  64. package/src/elements/kitn-response-stream.stories.tsx +79 -0
  65. package/src/elements/kitn-source-list.stories.tsx +77 -0
  66. package/src/elements/kitn-source.stories.tsx +87 -0
  67. package/src/elements/kitn-text-shimmer.stories.tsx +63 -0
  68. package/src/elements/kitn-thinking-bar.stories.tsx +72 -0
  69. package/src/elements/kitn-tool.stories.tsx +88 -0
  70. package/src/elements/kitn-voice-input.stories.tsx +87 -0
  71. package/src/elements/loader.tsx +25 -0
  72. package/src/elements/markdown.tsx +38 -0
  73. package/src/elements/message-skills.tsx +22 -0
  74. package/src/elements/message.tsx +125 -0
  75. package/src/elements/model-switcher.tsx +35 -0
  76. package/src/elements/prompt-input.tsx +83 -7
  77. package/src/elements/prompt-suggestions.tsx +58 -0
  78. package/src/elements/reasoning.tsx +50 -0
  79. package/src/elements/register.ts +31 -0
  80. package/src/elements/response-stream.tsx +40 -0
  81. package/src/elements/source.tsx +67 -0
  82. package/src/elements/text-shimmer.tsx +28 -0
  83. package/src/elements/thinking-bar.tsx +34 -0
  84. package/src/elements/tool.tsx +23 -0
  85. package/src/elements/voice-input.tsx +41 -0
  86. package/src/index.ts +0 -1
  87. package/src/primitives/chat-config.tsx +2 -2
  88. package/src/stories/docs/Accessibility.mdx +119 -0
  89. package/src/stories/docs/ForAIAgents.mdx +93 -0
  90. package/src/stories/docs/GettingStarted.mdx +2 -2
  91. package/src/stories/docs/Installation.mdx +2 -2
  92. package/src/stories/docs/Integrations.mdx +415 -15
  93. package/src/stories/docs/Introduction.mdx +5 -5
  94. package/src/stories/docs/Theming.mdx +1 -1
  95. package/src/stories/typography.stories.tsx +78 -0
  96. package/src/ui/button.tsx +1 -1
  97. package/src/ui/collapsible.tsx +119 -8
  98. package/src/ui/dropdown.tsx +177 -12
  99. package/src/ui/hover-card.tsx +147 -26
  100. package/src/ui/overlay.tsx +151 -0
  101. package/src/ui/textarea.tsx +1 -1
  102. package/src/ui/tooltip.stories.tsx +1 -1
  103. package/src/ui/tooltip.tsx +59 -13
  104. package/src/utils/cn.ts +19 -1
  105. package/theme.css +72 -43
  106. package/src/ui/dialog.tsx +0 -21
@@ -0,0 +1,151 @@
1
+ import {
2
+ createSignal, createEffect, onCleanup, splitProps, type Accessor, type JSX,
3
+ } from 'solid-js';
4
+ import { Dynamic } from 'solid-js/web';
5
+ import {
6
+ computePosition, autoUpdate, offset, flip, shift, arrow, type Placement,
7
+ } from '@floating-ui/dom';
8
+
9
+ /**
10
+ * Keep a node mounted through its CSS exit animation.
11
+ * Open -> present=true, state='open' (enter animation via base classes)
12
+ * Close -> state='closed' (data-closed triggers tw-animate-css animate-out),
13
+ * then unmount on `animationend`. If no animation is defined
14
+ * (e.g. jsdom), unmount on the next microtask.
15
+ */
16
+ export function createPresence(show: Accessor<boolean>) {
17
+ const [present, setPresent] = createSignal(show());
18
+ const [state, setState] = createSignal<'open' | 'closed'>(show() ? 'open' : 'closed');
19
+ let node: Element | undefined;
20
+ let generation = 0;
21
+ const setRef = (el: Element) => { node = el; };
22
+
23
+ createEffect((prev: boolean | undefined) => {
24
+ const visible = show();
25
+ if (visible) {
26
+ generation++;
27
+ setPresent(true);
28
+ setState('open');
29
+ } else if (prev) {
30
+ setState('closed');
31
+ const el = node;
32
+ const hasAnim = el && (() => {
33
+ const cs = getComputedStyle(el);
34
+ return cs.animationName !== 'none' && parseFloat(cs.animationDuration || '0') > 0;
35
+ })();
36
+ if (!el || !hasAnim) {
37
+ const gen = ++generation;
38
+ queueMicrotask(() => { if (gen === generation) setPresent(false); });
39
+ return visible;
40
+ }
41
+ const animEl = el as HTMLElement;
42
+ const onEnd = (e: AnimationEvent) => {
43
+ if (e.target !== animEl) return;
44
+ animEl.removeEventListener('animationend', onEnd);
45
+ setPresent(false);
46
+ };
47
+ animEl.addEventListener('animationend', onEnd);
48
+ onCleanup(() => animEl.removeEventListener('animationend', onEnd));
49
+ }
50
+ return visible;
51
+ });
52
+
53
+ return { present, state, setRef };
54
+ }
55
+
56
+ export type AsTag = string | ((props: Record<string, any>) => JSX.Element);
57
+
58
+ /**
59
+ * Polymorphic element. `as` may be a tag name (default 'span') or a render
60
+ * function that receives the forwarded props (Kobalte-compatible `as={fn}`).
61
+ * Uses splitProps (NOT destructuring) so reactive forwarded props such as
62
+ * aria-expanded stay reactive. All extra props (incl. `ref`, event handlers,
63
+ * aria-*) are forwarded. `children` is left in `rest` so it forwards naturally.
64
+ */
65
+ export function As(props: { as?: AsTag; children?: JSX.Element; [k: string]: any }) {
66
+ const [local, rest] = splitProps(props, ['as']);
67
+ if (typeof local.as === 'function') return local.as(rest);
68
+ return <Dynamic component={local.as ?? 'span'} {...rest} />;
69
+ }
70
+
71
+ export interface UsePositionOptions {
72
+ placement?: Placement;
73
+ gutter?: number;
74
+ arrowEl?: Accessor<HTMLElement | undefined>;
75
+ }
76
+
77
+ /**
78
+ * Position `floating` relative to `reference` using fixed strategy + autoUpdate,
79
+ * so the element tracks the trigger on scroll/resize (fix DD-2). Writes
80
+ * position into the returned `pos` signal; caller applies it as inline style.
81
+ *
82
+ * `options` (placement/gutter) are read at setup time — pass static values;
83
+ * reactive option changes won't reposition until the next autoUpdate tick.
84
+ */
85
+ export function usePosition(
86
+ reference: Accessor<HTMLElement | undefined>,
87
+ floating: Accessor<HTMLElement | undefined>,
88
+ options: UsePositionOptions = {},
89
+ ) {
90
+ const [pos, setPos] = createSignal<{ x: number; y: number; placement: Placement }>(
91
+ { x: 0, y: 0, placement: options.placement ?? 'bottom' },
92
+ );
93
+ const [arrowPos, setArrowPos] = createSignal<{ x?: number; y?: number }>({});
94
+
95
+ createEffect(() => {
96
+ const ref = reference();
97
+ const float = floating();
98
+ if (!ref || !float) return;
99
+ const update = () => {
100
+ const middleware = [offset(options.gutter ?? 8), flip(), shift({ padding: 8 })];
101
+ const aEl = options.arrowEl?.();
102
+ if (aEl) middleware.push(arrow({ element: aEl }));
103
+ computePosition(ref, float, {
104
+ placement: options.placement ?? 'bottom',
105
+ strategy: 'fixed',
106
+ middleware,
107
+ }).then(({ x, y, placement, middlewareData }) => {
108
+ setPos({ x, y, placement });
109
+ if (middlewareData.arrow) setArrowPos({ x: middlewareData.arrow.x, y: middlewareData.arrow.y });
110
+ });
111
+ };
112
+ const cleanup = autoUpdate(ref, float, update);
113
+ onCleanup(cleanup);
114
+ });
115
+
116
+ return { pos, arrowPos };
117
+ }
118
+
119
+ export type DismissReason = 'escape' | 'outside';
120
+
121
+ export interface UseDismissOptions {
122
+ enabled: Accessor<boolean>;
123
+ onDismiss: (reason: DismissReason) => void;
124
+ /** Elements considered "inside" (trigger + content). Pointerdown outside all of them dismisses. */
125
+ refs: () => (HTMLElement | undefined)[];
126
+ }
127
+
128
+ /**
129
+ * Escape key + outside-pointerdown dismissal. Does NOT lock page scroll (fix DD-1).
130
+ *
131
+ * `onDismiss` and `refs` are captured at call time (component setup), which is
132
+ * fine in SolidJS since components don't re-run — ensure they close over mutable
133
+ * variables, not stale values.
134
+ */
135
+ export function useDismiss(opts: UseDismissOptions) {
136
+ createEffect(() => {
137
+ if (!opts.enabled()) return;
138
+ const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') opts.onDismiss('escape'); };
139
+ const onPointer = (e: PointerEvent) => {
140
+ const target = e.target as Node;
141
+ const inside = opts.refs().some((el) => el && el.contains(target));
142
+ if (!inside) opts.onDismiss('outside');
143
+ };
144
+ document.addEventListener('keydown', onKey);
145
+ document.addEventListener('pointerdown', onPointer, true);
146
+ onCleanup(() => {
147
+ document.removeEventListener('keydown', onKey);
148
+ document.removeEventListener('pointerdown', onPointer, true);
149
+ });
150
+ });
151
+ }
@@ -13,7 +13,7 @@ export function Textarea(props: TextareaProps) {
13
13
  return (
14
14
  <textarea
15
15
  ref={local.autoResize !== false ? ref : undefined}
16
- class={cn('w-full resize-none bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none', local.class)}
16
+ class={cn('w-full resize-none rounded-md bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring', local.class)}
17
17
  rows={1}
18
18
  {...rest}
19
19
  />
@@ -11,7 +11,7 @@ const meta = {
11
11
  docs: {
12
12
  description: {
13
13
  component: [
14
- 'A small floating label that appears on hover/focus of its trigger element, built on Kobalte `Tooltip` with an arrow.',
14
+ 'A small floating label that appears on hover/focus of its trigger element, built with a DIY overlay-core implementation (no third-party dependency, no arrow).',
15
15
  '**When to use:** to clarify the purpose of icon-only buttons or terse controls — short, supplementary hints that are not essential to complete the action.',
16
16
  '**How to use:** wrap a single interactive `children` element and set `content` to the hint text. The child becomes the trigger.',
17
17
  '**Placement:** toolbars, message action rows, and any compact icon control where a label would not otherwise fit.',
@@ -1,22 +1,68 @@
1
- import { Tooltip as KTooltip } from '@kobalte/core/tooltip';
2
- import { type JSX, splitProps } from 'solid-js';
1
+ import { createSignal, createUniqueId, onCleanup, Show, type JSX, splitProps } from 'solid-js';
2
+ import { Portal } from 'solid-js/web';
3
3
  import { cn } from '../utils/cn';
4
4
  import { useChatConfig } from '../primitives/chat-config';
5
+ import { createPresence, usePosition, useDismiss, As } from './overlay';
5
6
 
6
- export interface TooltipProps { content: string; children: JSX.Element; class?: string; }
7
+ export interface TooltipProps { content: string; children: JSX.Element; class?: string; openDelay?: number; }
7
8
 
8
9
  export function Tooltip(props: TooltipProps) {
9
- const [local] = splitProps(props, ['content', 'children', 'class']);
10
+ const [local] = splitProps(props, ['content', 'children', 'class', 'openDelay']);
10
11
  const config = useChatConfig();
12
+ const id = createUniqueId();
13
+ const [open, setOpen] = createSignal(false);
14
+ const [triggerEl, setTriggerEl] = createSignal<HTMLElement>();
15
+ const [contentEl, setContentEl] = createSignal<HTMLElement>();
16
+ let timer: number | undefined;
17
+
18
+ const [pointerInside, setPointerInside] = createSignal(false);
19
+ const [focusInside, setFocusInside] = createSignal(false);
20
+
21
+ const show = (delay = 0) => {
22
+ clearTimeout(timer);
23
+ if (delay) timer = window.setTimeout(() => setOpen(true), delay);
24
+ else setOpen(true);
25
+ };
26
+ const hide = () => { clearTimeout(timer); setOpen(false); };
27
+ const maybeHide = () => { if (!pointerInside() && !focusInside()) hide(); };
28
+ onCleanup(() => clearTimeout(timer));
29
+
30
+ const presence = createPresence(open);
31
+ const position = usePosition(triggerEl, contentEl, { placement: 'top', gutter: 6 });
32
+ useDismiss({ enabled: open, onDismiss: hide, refs: () => [triggerEl(), contentEl()] });
33
+
11
34
  return (
12
- <KTooltip>
13
- <KTooltip.Trigger as="span">{local.children}</KTooltip.Trigger>
14
- <KTooltip.Portal mount={config.portalMount()}>
15
- <KTooltip.Content class={cn('z-50 rounded-md bg-foreground px-2.5 py-1 text-xs text-background shadow-md animate-in fade-in-0 zoom-in-95', local.class)}>
16
- <KTooltip.Arrow />
17
- {local.content}
18
- </KTooltip.Content>
19
- </KTooltip.Portal>
20
- </KTooltip>
35
+ <>
36
+ <As
37
+ as="span"
38
+ ref={setTriggerEl}
39
+ aria-describedby={open() ? id : undefined}
40
+ onPointerEnter={() => { setPointerInside(true); show(local.openDelay ?? 600); }}
41
+ onPointerLeave={() => { setPointerInside(false); maybeHide(); }}
42
+ onFocusIn={() => { setFocusInside(true); show(); }}
43
+ onFocusOut={(e: FocusEvent) => { const t = triggerEl(); if (t && t.contains(e.relatedTarget as Node)) return; setFocusInside(false); maybeHide(); }}
44
+ >
45
+ {local.children}
46
+ </As>
47
+ <Show when={presence.present()}>
48
+ <Portal mount={config.portalMount()}>
49
+ <div
50
+ ref={(el) => { setContentEl(el); presence.setRef(el); }}
51
+ id={id}
52
+ role="tooltip"
53
+ data-expanded={presence.state() === 'open' ? '' : undefined}
54
+ data-closed={presence.state() === 'closed' ? '' : undefined}
55
+ style={{ position: 'fixed', left: `${position.pos().x}px`, top: `${position.pos().y}px`, 'pointer-events': 'none' }}
56
+ class={cn(
57
+ 'z-50 rounded-md bg-foreground px-2.5 py-1 text-xs text-background shadow-md',
58
+ 'animate-in fade-in-0 zoom-in-95 data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95',
59
+ local.class,
60
+ )}
61
+ >
62
+ {local.content}
63
+ </div>
64
+ </Portal>
65
+ </Show>
66
+ </>
21
67
  );
22
68
  }
package/src/utils/cn.ts CHANGED
@@ -1,5 +1,23 @@
1
1
  import { clsx, type ClassValue } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
2
+ import { extendTailwindMerge } from 'tailwind-merge';
3
+
4
+ // The kit defines custom font-size utilities via @theme tokens in theme.css:
5
+ // text-caption / text-meta / text-body / text-title. tailwind-merge has no way
6
+ // to know these are font sizes, so by default it buckets e.g. `text-body` with
7
+ // text COLORS and drops a real color (`text-transparent`, `text-foreground`, …)
8
+ // whenever both appear in the same cn() call — which silently broke TextShimmer
9
+ // inside the web components (the element adds `text-body`, dropping
10
+ // `text-transparent`, so the gradient stayed hidden behind opaque text).
11
+ //
12
+ // Register them in the `font-size` group so they conflict only with other font
13
+ // sizes (text-xs/sm/base/lg/…) and never with text colors.
14
+ const twMerge = extendTailwindMerge({
15
+ extend: {
16
+ classGroups: {
17
+ 'font-size': [{ text: ['caption', 'meta', 'body', 'title'] }],
18
+ },
19
+ },
20
+ });
3
21
 
4
22
  export function cn(...inputs: ClassValue[]) {
5
23
  return twMerge(clsx(inputs));
package/theme.css CHANGED
@@ -9,36 +9,59 @@
9
9
  @custom-variant dark (&:is(.dark *));
10
10
 
11
11
  @theme {
12
- --color-background: hsl(0 0% 100%);
13
- --color-foreground: hsl(240 10% 3.9%);
14
- --color-card: hsl(0 0% 100%);
15
- --color-card-foreground: hsl(240 10% 3.9%);
16
- --color-popover: hsl(0 0% 100%);
17
- --color-popover-foreground: hsl(240 10% 3.9%);
18
- --color-primary: hsl(240 5.9% 10%);
19
- --color-primary-foreground: hsl(0 0% 98%);
20
- --color-secondary: hsl(240 4.8% 95.9%);
21
- --color-secondary-foreground: hsl(240 5.9% 10%);
22
- --color-muted: hsl(240 4.8% 95.9%);
23
- --color-muted-foreground: hsl(240 3.8% 46.1%);
24
- --color-accent: hsl(240 4.8% 95.9%);
25
- --color-accent-foreground: hsl(240 5.9% 10%);
26
- --color-destructive: hsl(0 84.2% 60.2%);
27
- --color-destructive-foreground: hsl(0 0% 98%);
28
- --color-border: hsl(240 5.9% 90%);
29
- --color-input: hsl(240 5.9% 90%);
30
- --color-ring: hsl(240 10% 3.9%);
31
- --color-sidebar: hsl(0 0% 100%);
12
+ --color-background: var(--kitn-color-background, hsl(0 0% 100%));
13
+ --color-foreground: var(--kitn-color-foreground, hsl(240 10% 3.9%));
14
+ --color-card: var(--kitn-color-card, hsl(0 0% 100%));
15
+ --color-card-foreground: var(--kitn-color-card-foreground, hsl(240 10% 3.9%));
16
+ --color-popover: var(--kitn-color-popover, hsl(0 0% 100%));
17
+ --color-popover-foreground: var(--kitn-color-popover-foreground, hsl(240 10% 3.9%));
18
+ --color-primary: var(--kitn-color-primary, hsl(240 5.9% 10%));
19
+ --color-primary-foreground: var(--kitn-color-primary-foreground, hsl(0 0% 98%));
20
+ --color-secondary: var(--kitn-color-secondary, hsl(240 4.8% 95.9%));
21
+ --color-secondary-foreground: var(--kitn-color-secondary-foreground, hsl(240 5.9% 10%));
22
+ --color-muted: var(--kitn-color-muted, hsl(240 4.8% 95.9%));
23
+ --color-muted-foreground: var(--kitn-color-muted-foreground, hsl(240 3.8% 43%));
24
+ --color-accent: var(--kitn-color-accent, hsl(240 4.8% 95.9%));
25
+ --color-accent-foreground: var(--kitn-color-accent-foreground, hsl(240 5.9% 10%));
26
+ --color-destructive: var(--kitn-color-destructive, hsl(0 84.2% 60.2%));
27
+ --color-destructive-foreground: var(--kitn-color-destructive-foreground, hsl(0 0% 98%));
28
+ --color-border: var(--kitn-color-border, hsl(240 5.9% 90%));
29
+ --color-input: var(--kitn-color-input, hsl(240 5.9% 90%));
30
+ --color-ring: var(--kitn-color-ring, hsl(240 10% 3.9%));
31
+ --color-sidebar: var(--kitn-color-sidebar, hsl(0 0% 100%));
32
32
 
33
33
  /* Inline `code` accent. Blue text on a translucent blue chip. */
34
- --color-code-foreground: hsl(224.3 76.3% 48%);
34
+ --color-code-foreground: var(--kitn-color-code-foreground, hsl(224.3 76.3% 48%));
35
35
 
36
- --radius: 0.6rem;
36
+ /* Tool/status chip hues. Each chip is hue text over a 15% translucent fill of
37
+ the SAME hue (set in tool.tsx). The bare hue is too light to reach WCAG AA
38
+ (4.5:1) on the faint fill in LIGHT mode, so the light values are darkened;
39
+ dark mode keeps brighter hues for AA on the dark fill. Override via
40
+ --kitn-color-tool-* to retheme. */
41
+ --color-tool-blue: var(--kitn-color-tool-blue, hsl(217 91% 38%));
42
+ --color-tool-amber: var(--kitn-color-tool-amber, hsl(38 92% 28%));
43
+ --color-tool-green: var(--kitn-color-tool-green, hsl(142 71% 26%));
44
+ --color-tool-red: var(--kitn-color-tool-red, hsl(0 72% 42%));
45
+
46
+ --radius: var(--kitn-radius, 0.6rem);
37
47
  --radius-sm: calc(var(--radius) - 4px);
38
48
  --radius-md: calc(var(--radius) - 2px);
39
49
  --radius-lg: var(--radius);
40
50
  --radius-xl: calc(var(--radius) + 4px);
41
51
 
52
+ /* Typography scale — semantic sizes shared by all components. Each generates a
53
+ Tailwind utility (text-caption / text-meta / text-body / text-title). To
54
+ restyle the kit's typography, override the namespaced --kitn-text-* token on
55
+ :root (e.g. `--kitn-text-body: 0.9375rem`) — it pierces the Shadow DOM via the
56
+ var() fallback, exactly like the --kitn-color-* tokens. The bare --text-*
57
+ names stay internal so a host's own --text-* can't collide. (Message/markdown/
58
+ input reading size also scales with the `proseSize` prop; these tokens cover
59
+ the fixed chrome & controls.) */
60
+ --text-caption: var(--kitn-text-caption, 0.6875rem); --text-caption--line-height: 1rem; /* 11px — micro labels, badges, sub-counts */
61
+ --text-meta: var(--kitn-text-meta, 0.75rem); --text-meta--line-height: 1.1rem; /* 12px — controls, toggles, switchers, captions */
62
+ --text-body: var(--kitn-text-body, 0.875rem); --text-body--line-height: 1.45rem; /* 14px — primary reading text */
63
+ --text-title: var(--kitn-text-title, 1rem); --text-title--line-height: 1.5rem; /* 16px — emphasis / headers */
64
+
42
65
  @keyframes typing { 0%,100% { transform: translateY(0); opacity: .5 } 50% { transform: translateY(-2px); opacity: 1 } }
43
66
  @keyframes loading-dots { 0%,100% { opacity: 0 } 50% { opacity: 1 } }
44
67
  @keyframes wave { 0%,100% { transform: scaleY(1) } 50% { transform: scaleY(.6) } }
@@ -56,27 +79,33 @@
56
79
  }
57
80
 
58
81
  .dark {
59
- --color-background: hsl(50 2% 9%);
60
- --color-foreground: hsl(0 0% 98%);
61
- --color-card: hsl(240 10% 3.9%);
62
- --color-card-foreground: hsl(0 0% 98%);
63
- --color-popover: hsl(240 10% 3.9%);
64
- --color-popover-foreground: hsl(0 0% 98%);
65
- --color-primary: hsl(0 0% 98%);
66
- --color-primary-foreground: hsl(240 5.9% 10%);
67
- --color-secondary: hsl(240 3.7% 15.9%);
68
- --color-secondary-foreground: hsl(0 0% 98%);
69
- --color-muted: hsl(240 3.7% 15.9%);
70
- --color-muted-foreground: hsl(240 5% 64.9%);
71
- --color-accent: hsl(240 3.7% 15.9%);
72
- --color-accent-foreground: hsl(0 0% 98%);
73
- --color-destructive: hsl(0 62.8% 30.6%);
74
- --color-destructive-foreground: hsl(0 0% 98%);
75
- --color-border: hsl(240 3.7% 15.9%);
76
- --color-input: hsl(240 3.7% 15.9%);
77
- --color-ring: hsl(240 4.9% 83.9%);
78
- --color-sidebar: hsl(50 2% 7%);
79
- --color-code-foreground: hsl(213 94% 78%);
82
+ --color-background: var(--kitn-color-background, hsl(50 2% 9%));
83
+ --color-foreground: var(--kitn-color-foreground, hsl(0 0% 98%));
84
+ --color-card: var(--kitn-color-card, hsl(45 4% 12%));
85
+ --color-card-foreground: var(--kitn-color-card-foreground, hsl(0 0% 98%));
86
+ --color-popover: var(--kitn-color-popover, hsl(45 4% 12%));
87
+ --color-popover-foreground: var(--kitn-color-popover-foreground, hsl(0 0% 98%));
88
+ --color-primary: var(--kitn-color-primary, hsl(0 0% 98%));
89
+ --color-primary-foreground: var(--kitn-color-primary-foreground, hsl(45 4% 11%));
90
+ --color-secondary: var(--kitn-color-secondary, hsl(45 4% 17%));
91
+ --color-secondary-foreground: var(--kitn-color-secondary-foreground, hsl(0 0% 98%));
92
+ --color-muted: var(--kitn-color-muted, hsl(45 4% 17%));
93
+ --color-muted-foreground: var(--kitn-color-muted-foreground, hsl(45 4% 64%));
94
+ --color-accent: var(--kitn-color-accent, hsl(45 4% 17%));
95
+ --color-accent-foreground: var(--kitn-color-accent-foreground, hsl(0 0% 98%));
96
+ --color-destructive: var(--kitn-color-destructive, hsl(0 62.8% 30.6%));
97
+ --color-destructive-foreground: var(--kitn-color-destructive-foreground, hsl(0 0% 98%));
98
+ --color-border: var(--kitn-color-border, hsl(45 4% 17%));
99
+ --color-input: var(--kitn-color-input, hsl(45 4% 17%));
100
+ --color-ring: var(--kitn-color-ring, hsl(45 5% 72%));
101
+ --color-sidebar: var(--kitn-color-sidebar, hsl(50 2% 7%));
102
+ --color-code-foreground: var(--kitn-color-code-foreground, hsl(213 94% 78%));
103
+
104
+ /* Tool/status chip hues — dark mode. Brighter hues reach AA on the dark fill. */
105
+ --color-tool-blue: var(--kitn-color-tool-blue, hsl(217 91% 70%));
106
+ --color-tool-amber: var(--kitn-color-tool-amber, hsl(38 92% 50%));
107
+ --color-tool-green: var(--kitn-color-tool-green, hsl(142 71% 45%));
108
+ --color-tool-red: var(--kitn-color-tool-red, hsl(0 84% 70%));
80
109
  }
81
110
 
82
111
  /* Self-contained markdown styling — replaces the typography plugin's `prose`.
package/src/ui/dialog.tsx DELETED
@@ -1,21 +0,0 @@
1
- import { Dialog as KDialog } from '@kobalte/core/dialog';
2
- import { type JSX } from 'solid-js';
3
- import { cn } from '../utils/cn';
4
- import { useChatConfig } from '../primitives/chat-config';
5
-
6
- export const Dialog = KDialog;
7
- export const DialogTrigger = KDialog.Trigger;
8
-
9
- export function DialogContent(props: { children: JSX.Element; class?: string; title: string }) {
10
- const config = useChatConfig();
11
- return (
12
- <KDialog.Portal mount={config.portalMount()}>
13
- <KDialog.Overlay class="fixed inset-0 z-50 bg-black/50 animate-in fade-in-0" />
14
- <KDialog.Content class={cn('fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 rounded-xl bg-card p-6 shadow-xl animate-in fade-in-0 zoom-in-95 w-full max-w-md', props.class)}>
15
- <KDialog.Title class="text-lg font-semibold">{props.title}</KDialog.Title>
16
- {props.children}
17
- <KDialog.CloseButton class="absolute right-4 top-4 text-muted-foreground hover:text-foreground" />
18
- </KDialog.Content>
19
- </KDialog.Portal>
20
- );
21
- }