@atmos.build/ui 0.1.6 → 0.1.8

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.
@@ -7,7 +7,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
7
7
  import { MiddleTruncate } from './middle-truncate.js';
8
8
  import { StickyHeaderFade } from './sticky-header-fade.js';
9
9
  export function DetailTopBar({ title, trail, meta, actions, className, contentClassName, leftClassName, actionsClassName, chromeFont, headingFont, dataTestId, fade = true, }) {
10
- return (_jsxs("div", { className: cn('bg-background/95 sticky top-0 z-50 flex h-10 items-center justify-between gap-3 px-3 backdrop-blur', chromeFont, className), "data-slot": "detail-top-bar", "data-testid": dataTestId, children: [_jsxs("div", { className: cn('flex min-w-0 flex-1 items-center gap-3 overflow-hidden', contentClassName), children: [_jsx("div", { className: cn('flex min-w-0 flex-1 items-center overflow-hidden', leftClassName), children: trail ?? (_jsx("h1", { className: cn('text-foreground min-w-0 truncate text-[13px] leading-5 font-medium', headingFont), children: title })) }), meta ? (_jsx("div", { className: "text-muted-foreground min-w-0 shrink truncate font-mono text-[11.5px]", children: meta })) : null] }), actions ? (_jsx("div", { className: cn('flex shrink-0 items-center gap-1', actionsClassName), children: actions })) : null, fade ? _jsx(StickyHeaderFade, { "data-slot": "detail-top-bar-fade" }) : null] }));
10
+ return (_jsxs("div", { className: cn('bg-background/95 sticky top-0 z-50 flex h-10 items-center justify-between gap-3 px-4 backdrop-blur', chromeFont, className), "data-slot": "detail-top-bar", "data-testid": dataTestId, children: [_jsxs("div", { className: cn('flex min-w-0 flex-1 items-center gap-3 overflow-hidden', contentClassName), children: [_jsx("div", { className: cn('flex min-w-0 flex-1 items-center overflow-hidden', leftClassName), children: trail ?? (_jsx("h1", { className: cn('text-foreground min-w-0 truncate text-[13px] leading-5 font-medium', headingFont), children: title })) }), meta ? (_jsx("div", { className: "text-muted-foreground min-w-0 shrink truncate font-mono text-[11.5px]", children: meta })) : null] }), actions ? (_jsx("div", { className: cn('flex shrink-0 items-center gap-1', actionsClassName), children: actions })) : null, fade ? _jsx(StickyHeaderFade, { "data-slot": "detail-top-bar-fade" }) : null] }));
11
11
  }
12
12
  export function DetailBreadcrumb({ items, ariaLabel, className, separator = '/', collapseMiddle = false, tailItems = 2, collapsedAriaLabel, dataTestId, }) {
13
13
  const collapsedItems = collapseMiddle ? collapsedMiddleItems(items, tailItems) : [];
@@ -6,6 +6,7 @@ export interface NautAppearance {
6
6
  shape: NautShape;
7
7
  color: NautColor;
8
8
  }
9
+ export type NautAnimationRate = 'efficient' | 'smooth';
9
10
  export interface NautAvatarProps extends Omit<React.SVGProps<SVGSVGElement>, 'ref'> {
10
11
  shape?: NautShape;
11
12
  color?: NautColor;
@@ -19,6 +20,7 @@ export interface NautAvatarProps extends Omit<React.SVGProps<SVGSVGElement>, 're
19
20
  } | null;
20
21
  /** Glance at a mouse that comes near the face. Ignored once `gaze` is given. */
21
22
  followPointer?: boolean;
23
+ animationRate?: NautAnimationRate;
22
24
  /** Any stable id — keeps a list of Nauts from blinking in unison. */
23
25
  seed?: string | number;
24
26
  }
@@ -10,6 +10,7 @@ export { NAUT_COLORS, NAUT_COLOR_KEYS, NAUT_EXPRESSIONS, NAUT_GESTURES, NAUT_SHA
10
10
  const RECT_MAX_AGE_MS = 500;
11
11
  const IDLE_FRAME_MS = 1000 / 3;
12
12
  const RESPONSIVE_FRAME_MS = 1000 / 20;
13
+ const SMOOTH_FRAME_MS = 1000 / 60;
13
14
  const POINTER_SETTLE_MS = 350;
14
15
  /** Where the mouse is, shared by every face so one listener serves the page. */
15
16
  const pointer = {
@@ -75,7 +76,11 @@ const ticker = {
75
76
  if (!entry.visible)
76
77
  continue;
77
78
  const responsive = (entry.follow && pointerResponsive) || entry.engine.needsResponsiveFrameRate;
78
- const frameMs = responsive ? RESPONSIVE_FRAME_MS : IDLE_FRAME_MS;
79
+ const frameMs = entry.smooth
80
+ ? SMOOTH_FRAME_MS
81
+ : responsive
82
+ ? RESPONSIVE_FRAME_MS
83
+ : IDLE_FRAME_MS;
79
84
  if (now - entry.lastUpdate < frameMs)
80
85
  continue;
81
86
  const dt = Math.min((now - entry.lastUpdate) / 1000, 0.1);
@@ -111,7 +116,7 @@ const seedNumber = (seed) => typeof seed === 'number' ? seed : hashSeed(seed ??
111
116
  * look around. The eyes are holes, so set `--naut-avatar-eye` to the surface the
112
117
  * avatar sits on (defaults to `--background`).
113
118
  */
114
- export const NautAvatar = React.forwardRef(function NautAvatar({ shape = 'circle', color = 'ink', expression = 'neutral', idle = true, gaze = null, followPointer = true, seed, className, style, ...props }, forwardedRef) {
119
+ export const NautAvatar = React.forwardRef(function NautAvatar({ shape = 'circle', color = 'ink', expression = 'neutral', idle = true, gaze = null, followPointer = true, animationRate = 'efficient', seed, className, style, ...props }, forwardedRef) {
115
120
  const reducedMotion = useReducedMotion();
116
121
  const animated = idle && !reducedMotion;
117
122
  const svgRef = React.useRef(null);
@@ -126,6 +131,7 @@ export const NautAvatar = React.forwardRef(function NautAvatar({ shape = 'circle
126
131
  svg: null,
127
132
  visible: true,
128
133
  follow: false,
134
+ smooth: false,
129
135
  rect: null,
130
136
  rectAt: -1,
131
137
  rectTime: 0,
@@ -141,7 +147,8 @@ export const NautAvatar = React.forwardRef(function NautAvatar({ shape = 'circle
141
147
  engine.idle = animated;
142
148
  engine.gazeTarget = gaze;
143
149
  entry.follow = followPointer && !gaze && animated;
144
- }, [engine, entry, shape, color, expression, animated, gaze, followPointer]);
150
+ entry.smooth = animationRate === 'smooth';
151
+ }, [engine, entry, shape, color, expression, animated, gaze, followPointer, animationRate]);
145
152
  React.useLayoutEffect(() => {
146
153
  if (!rootRef.current ||
147
154
  !bodyRef.current ||
@@ -191,5 +198,5 @@ export const NautAvatar = React.forwardRef(function NautAvatar({ shape = 'circle
191
198
  engine.settle();
192
199
  engine.update(0);
193
200
  }, [animated, engine, shape, color, expression]);
194
- return (_jsx("svg", { ref: svgRef, viewBox: NAUT_VIEWBOX, "data-slot": "naut-avatar", "data-shape": shape, "data-color": color, "data-expression": expression, "data-atmos-motion": animated ? 'running' : undefined, "data-atmos-motion-property": animated ? 'svg-attributes' : undefined, className: cn('block size-full overflow-visible select-none', className), style: style, "aria-hidden": props['aria-label'] ? undefined : true, ...props, children: _jsxs("g", { ref: rootRef, children: [_jsx("path", { ref: bodyRef }), _jsxs("g", { ref: eyesRef, children: [_jsx("path", { ref: eyeLRef, fill: "var(--naut-avatar-eye, var(--background))" }), _jsx("path", { ref: eyeRRef, fill: "var(--naut-avatar-eye, var(--background))" })] })] }) }));
201
+ return (_jsx("svg", { ref: svgRef, viewBox: NAUT_VIEWBOX, "data-slot": "naut-avatar", "data-shape": shape, "data-color": color, "data-expression": expression, "data-animation-rate": animationRate, "data-atmos-motion": animated ? 'running' : undefined, "data-atmos-motion-property": animated ? 'svg-attributes' : undefined, className: cn('block size-full overflow-visible select-none', className), style: style, "aria-hidden": props['aria-label'] ? undefined : true, ...props, children: _jsxs("g", { ref: rootRef, children: [_jsx("path", { ref: bodyRef }), _jsxs("g", { ref: eyesRef, children: [_jsx("path", { ref: eyeLRef, fill: "var(--naut-avatar-eye, var(--background))" }), _jsx("path", { ref: eyeRRef, fill: "var(--naut-avatar-eye, var(--background))" })] })] }) }));
195
202
  });
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import { type VariantProps } from 'class-variance-authority';
3
3
  declare const selectableCardVariants: (props?: ({
4
4
  layout?: "grid" | "row" | null | undefined;
5
+ appearance?: "list" | "card" | null | undefined;
5
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
7
  type SelectableCardOwnProps = {
7
8
  selected?: boolean;
@@ -19,6 +20,7 @@ type SelectableCardOwnProps = {
19
20
  export type SelectableCardProps = Omit<React.ComponentPropsWithoutRef<'button'>, keyof SelectableCardOwnProps | 'value'> & SelectableCardOwnProps & VariantProps<typeof selectableCardVariants>;
20
21
  declare const SelectableCard: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "value" | keyof SelectableCardOwnProps> & SelectableCardOwnProps & VariantProps<(props?: ({
21
22
  layout?: "grid" | "row" | null | undefined;
23
+ appearance?: "list" | "card" | null | undefined;
22
24
  } & import("class-variance-authority/types").ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
23
25
  export type SelectableCardGroupProps = Omit<React.ComponentPropsWithoutRef<'div'>, 'onChange'> & {
24
26
  value?: string | null;
@@ -30,4 +32,4 @@ declare function SelectableCardGroup({ value, onChange, orientation, className,
30
32
  declare const SelectableCardGroupItem: React.ForwardRefExoticComponent<Omit<SelectableCardProps, "role" | "selected"> & {
31
33
  value: string;
32
34
  } & React.RefAttributes<HTMLButtonElement>>;
33
- export { SelectableCard, SelectableCardGroup, SelectableCardGroupItem, selectableCardVariants, };
35
+ export { SelectableCard, SelectableCardGroup, SelectableCardGroupItem, selectableCardVariants };
@@ -12,10 +12,18 @@ const selectableCardVariants = cva([
12
12
  ], {
13
13
  variants: {
14
14
  layout: { row: 'items-center gap-3 p-3', grid: 'flex-col items-start gap-2 p-4' },
15
+ appearance: {
16
+ card: '',
17
+ list: [
18
+ 'border-transparent bg-transparent shadow-none',
19
+ 'hover:border-transparent hover:bg-accent/60',
20
+ 'data-[selected=true]:border-transparent data-[selected=true]:bg-accent/70 data-[selected=true]:shadow-none data-[selected=true]:ring-0',
21
+ ],
22
+ },
15
23
  },
16
- defaultVariants: { layout: 'row' },
24
+ defaultVariants: { layout: 'row', appearance: 'card' },
17
25
  });
18
- const SelectableCard = React.forwardRef(function SelectableCard({ selected = false, onSelect, icon, title, description, trailing, hideIndicator = false, indicator = 'check', role = 'radio', layout = 'row', disabled, className, children, onClick, onKeyDown, ...props }, ref) {
26
+ const SelectableCard = React.forwardRef(function SelectableCard({ selected = false, onSelect, icon, title, description, trailing, hideIndicator = false, indicator = 'check', role = 'radio', layout = 'row', appearance = 'card', disabled, className, children, onClick, onKeyDown, ...props }, ref) {
19
27
  const handleClick = (event) => {
20
28
  onClick?.(event);
21
29
  if (!event.defaultPrevented && !disabled)
@@ -30,8 +38,10 @@ const SelectableCard = React.forwardRef(function SelectableCard({ selected = fal
30
38
  };
31
39
  const showIndicator = !hideIndicator && trailing === undefined;
32
40
  const trailingNode = trailing ??
33
- (showIndicator && indicator === 'radio' ? (_jsx("span", { "aria-hidden": true, className: cn('flex size-[18px] shrink-0 items-center justify-center rounded-full border transition-colors', layout === 'grid' && 'absolute end-3 top-3', selected ? 'border-primary' : 'border-border group-hover/selectable-card:border-primary/50'), children: _jsx("span", { className: cn('size-2.5 rounded-full bg-primary transition-transform', selected ? 'scale-100' : 'scale-0') }) })) : showIndicator ? (_jsx(Check, { "aria-hidden": true, className: cn('size-4 shrink-0 text-primary transition-opacity', layout === 'grid' && 'absolute end-3 top-3', selected ? 'opacity-100' : 'opacity-0'), strokeWidth: 2 })) : null);
34
- return (_jsxs("button", { ref: ref, type: "button", role: role, "aria-checked": selected, "aria-disabled": disabled || undefined, disabled: disabled, "data-slot": "selectable-card", "data-selected": selected, "data-layout": layout, onClick: handleClick, onKeyDown: handleKeyDown, className: cn(selectableCardVariants({ layout }), className), ...props, children: [icon != null ? (_jsx("span", { "data-slot": "selectable-card-icon", className: "flex shrink-0 items-center justify-center [&_svg:not([class*='size-'])]:size-4", children: icon })) : null, children, title != null || description != null ? (_jsxs("span", { "data-slot": "selectable-card-body", className: "min-w-0 flex-1", children: [title != null ? _jsx("span", { className: "block truncate text-sm font-medium", children: title }) : null, description != null ? (_jsx("span", { className: "mt-0.5 block text-xs leading-snug text-muted-foreground", children: description })) : null] })) : null, trailingNode] }));
41
+ (showIndicator && indicator === 'radio' ? (_jsx("span", { "aria-hidden": true, className: cn('flex size-[18px] shrink-0 items-center justify-center rounded-full border transition-colors', layout === 'grid' && 'absolute end-3 top-3', selected
42
+ ? 'border-primary'
43
+ : 'border-border group-hover/selectable-card:border-primary/50'), children: _jsx("span", { className: cn('bg-primary size-2.5 rounded-full transition-transform', selected ? 'scale-100' : 'scale-0') }) })) : showIndicator ? (_jsx(Check, { "aria-hidden": true, className: cn('text-primary size-4 shrink-0 transition-opacity', layout === 'grid' && 'absolute end-3 top-3', selected ? 'opacity-100' : 'opacity-0'), strokeWidth: 2 })) : null);
44
+ return (_jsxs("button", { ref: ref, type: "button", role: role, "aria-checked": selected, "aria-disabled": disabled || undefined, disabled: disabled, "data-slot": "selectable-card", "data-selected": selected, "data-layout": layout, "data-appearance": appearance, onClick: handleClick, onKeyDown: handleKeyDown, className: cn(selectableCardVariants({ layout, appearance }), className), ...props, children: [icon != null ? (_jsx("span", { "data-slot": "selectable-card-icon", className: "flex shrink-0 items-center justify-center [&_svg:not([class*='size-'])]:size-4", children: icon })) : null, children, title != null || description != null ? (_jsxs("span", { "data-slot": "selectable-card-body", className: "min-w-0 flex-1", children: [title != null ? (_jsx("span", { className: "block truncate text-sm font-medium", children: title })) : null, description != null ? (_jsx("span", { className: "text-muted-foreground mt-0.5 block text-xs leading-snug", children: description })) : null] })) : null, trailingNode] }));
35
45
  });
36
46
  const SelectableCardGroupContext = React.createContext(null);
37
47
  function SelectableCardGroup({ value, onChange, orientation = 'vertical', className, children, onKeyDown, ...props }) {
@@ -63,4 +73,4 @@ const SelectableCardGroupItem = React.forwardRef(function SelectableCardGroupIte
63
73
  context?.onChange?.(value);
64
74
  }, ...props }));
65
75
  });
66
- export { SelectableCard, SelectableCardGroup, SelectableCardGroupItem, selectableCardVariants, };
76
+ export { SelectableCard, SelectableCardGroup, SelectableCardGroupItem, selectableCardVariants };
package/dist/index.d.ts CHANGED
@@ -68,7 +68,7 @@ export { PropertyPanel, type PropertyPanelProps, type PropertyPanelLabels, } fro
68
68
  export { SortableList, SortableItem, SortableRow, DragHandle, DragPreview, useSortableHandle, useSortableRow, reorderById, type SortableListProps, type ReorderDetail, type DragHandleProps, DndContext, KeyboardSensor, PointerSensor, SortableContext, arrayMove, closestCenter, sortableKeyboardCoordinates, useDraggable, useDroppable, useSensor, useSensors, verticalListSortingStrategy, type DragEndEvent, type DragOverEvent, type DragStartEvent, } from './components/sortable.js';
69
69
  export { VoiceLine, type VoiceLineProps, type VoiceLineState } from './components/voice-line.js';
70
70
  export { ShimmerText, type ShimmerTextProps } from './components/shimmer-text.js';
71
- export { NautAvatar, NAUT_COLORS, NAUT_COLOR_KEYS, NAUT_EXPRESSIONS, NAUT_GESTURES, NAUT_SHAPES, isNautColor, isNautShape, nautAppearanceForSeed, type NautAppearance, type NautAvatarHandle, type NautAvatarProps, type NautColor, type NautExpression, type NautGesture, type NautShape, } from './components/naut-avatar.js';
71
+ export { NautAvatar, NAUT_COLORS, NAUT_COLOR_KEYS, NAUT_EXPRESSIONS, NAUT_GESTURES, NAUT_SHAPES, isNautColor, isNautShape, nautAppearanceForSeed, type NautAnimationRate, type NautAppearance, type NautAvatarHandle, type NautAvatarProps, type NautColor, type NautExpression, type NautGesture, type NautShape, } from './components/naut-avatar.js';
72
72
  export { NautScene, NAUT_SCENES, type NautSceneDef, type NautSceneFace, type NautSceneName, type NautSceneProps, } from './components/naut-scene.js';
73
73
  export { TranscriptionOverlay, type TranscriptionOverlayLabels, type TranscriptionOverlayProps, type TranscriptionOverlaySelector, type TranscriptionOverlayState, } from './components/transcription-overlay.js';
74
74
  export { TimestampedTranscript, type TimestampedTranscriptLabels, type TimestampedTranscriptProps, type TimestampedTranscriptUtterance, type TimestampedTranscriptWord, } from './components/timestamped-transcript.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atmos.build/ui",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "The complete atmOS React component system and MCP App authoring helpers.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",