@stll/ui 0.58.0 → 0.59.1

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.
@@ -9,18 +9,28 @@ export type DayCalendarTone = {
9
9
  } | {
10
10
  type: "neutral";
11
11
  };
12
- /** Whether an entry is still provisional (drawn dashed) or final. */
13
- export type DayCalendarEntryState = "draft" | "final";
12
+ /**
13
+ * Whether an entry is final, still provisional (dashed), or only proposed:
14
+ * a suggestion is not an entry yet, so it is drawn lighter, dashed, and is a
15
+ * button the caller acts on.
16
+ */
17
+ export type DayCalendarEntryState = "draft" | "final" | "suggestion";
14
18
  type DayCalendarBlockFields = DayCalendarRange & {
15
19
  accessibleLabel: string;
16
- detail?: ReactNode;
20
+ caption?: ReactNode;
17
21
  id: string;
18
22
  title: ReactNode;
19
23
  tone: DayCalendarTone;
24
+ /** Shown on hover, for example the apps a `mixed` block merged. */
25
+ tooltip?: ReactNode;
20
26
  };
21
27
  export type DayCalendarBlock = (DayCalendarBlockFields & {
22
28
  kind: "entry";
23
- state: DayCalendarEntryState;
29
+ state: Exclude<DayCalendarEntryState, "suggestion">;
30
+ }) | (DayCalendarBlockFields & {
31
+ kind: "entry";
32
+ onActivate: () => void;
33
+ state: "suggestion";
24
34
  }) | (DayCalendarBlockFields & {
25
35
  kind: "mixed";
26
36
  }) | (DayCalendarBlockFields & {
@@ -1,7 +1,8 @@
1
1
  import { cn } from "../lib/utils.js";
2
+ import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "../components/tooltip.js";
2
3
  import { DAY_CALENDAR_SLOT_HEIGHT_PX, boundsHeight, extendRange, getDayBounds, getHourTicks, isRepeatedWallTime, layoutOverlaps, moveRange, offsetToTime, rangeFromDrag, slotAt, timeToOffset } from "./day-calendar.logic.js";
3
4
  import { resolveMatterColor } from "../lib/matter-colors.js";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
6
  import { useState } from "react";
6
7
  import { panic } from "better-result";
7
8
  import { Temporal } from "temporal-polyfill/full";
@@ -305,7 +306,7 @@ const ColumnBlocks = ({ blocks, grid }) => {
305
306
  }, block.id);
306
307
  });
307
308
  };
308
- /** Below this height a block puts its title and detail on one line. */
309
+ /** Below this height a block puts its title and caption on one line. */
309
310
  const TWO_LINE_BLOCK_MIN_PX = 40;
310
311
  /** A brief block paints at least this tall, unless the grid ends sooner. */
311
312
  const BLOCK_MIN_PX = 6;
@@ -316,7 +317,8 @@ const BLOCK_KIND_CLASSES = {
316
317
  };
317
318
  const ENTRY_STATE_CLASSES = {
318
319
  draft: "border border-dashed border-current/40",
319
- final: ""
320
+ final: "",
321
+ suggestion: "border border-dashed border-current/45 outline-none hover:ring-1 hover:ring-ring/50 focus-visible:ring-2 focus-visible:ring-ring"
320
322
  };
321
323
  const DayCalendarBlockView = ({ block, grid, lane, range }) => {
322
324
  const top = timeToOffset(range.startMs, grid);
@@ -327,36 +329,66 @@ const DayCalendarBlockView = ({ block, grid, lane, range }) => {
327
329
  insetBlockStart: top,
328
330
  insetInlineStart: `${String(lane.lane * 100 / lane.laneCount)}%`
329
331
  };
332
+ const className = cn("text-foreground flex h-full w-full min-w-0 overflow-hidden rounded-md px-2.5 text-start text-xs", blockSize < TWO_LINE_BLOCK_MIN_PX ? "flex-row items-center gap-2" : "flex-col gap-0.5 py-1", BLOCK_KIND_CLASSES[block.kind], block.kind === "entry" && ENTRY_STATE_CLASSES[block.state], block.tone.type === "matter" && "border-s-[3px]", block.tone.type === "neutral" && "bg-muted");
333
+ const content = blockSize < 16 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
334
+ className: "min-w-0 shrink-0 truncate font-medium",
335
+ children: block.title
336
+ }), block.caption === void 0 ? null : /* @__PURE__ */ jsx("span", {
337
+ className: "text-muted-foreground min-w-0 truncate tabular-nums",
338
+ children: block.caption
339
+ })] });
340
+ const shared = {
341
+ "aria-label": block.accessibleLabel,
342
+ className,
343
+ "data-block-id": block.id,
344
+ "data-kind": block.kind,
345
+ "data-slot": "day-calendar-block",
346
+ style: blockSurfaceStyle(block)
347
+ };
348
+ const surface = block.kind === "entry" && block.state === "suggestion" ? /* @__PURE__ */ jsx("button", {
349
+ ...shared,
350
+ "data-state": block.state,
351
+ onClick: block.onActivate,
352
+ onKeyDown: (event) => event.stopPropagation(),
353
+ onPointerDown: (event) => event.stopPropagation(),
354
+ type: "button",
355
+ children: content
356
+ }) : /* @__PURE__ */ jsx("article", {
357
+ ...shared,
358
+ "data-state": block.kind === "entry" ? block.state : void 0,
359
+ children: content
360
+ });
330
361
  return /* @__PURE__ */ jsx("div", {
331
- className: "pointer-events-none absolute px-1.5 py-px",
362
+ className: cn("absolute px-1.5 py-px", block.tooltip === void 0 && !(block.kind === "entry" && block.state === "suggestion") && "pointer-events-none"),
332
363
  style: placement,
333
- children: /* @__PURE__ */ jsxs("article", {
334
- "aria-label": block.accessibleLabel,
335
- className: cn("text-foreground flex h-full min-w-0 overflow-hidden rounded-md px-2.5 text-start text-xs", blockSize < TWO_LINE_BLOCK_MIN_PX ? "flex-row items-center gap-2" : "flex-col gap-0.5 py-1", BLOCK_KIND_CLASSES[block.kind], block.kind === "entry" && ENTRY_STATE_CLASSES[block.state], block.tone.type === "matter" && "border-s-[3px]", block.tone.type === "neutral" && "bg-muted"),
336
- "data-kind": block.kind,
337
- "data-slot": "day-calendar-block",
338
- style: blockSurfaceStyle(block),
339
- children: [/* @__PURE__ */ jsx("span", {
340
- className: "min-w-0 shrink-0 truncate font-medium",
341
- children: block.title
342
- }), block.detail === void 0 ? null : /* @__PURE__ */ jsx("span", {
343
- className: "text-muted-foreground min-w-0 truncate tabular-nums",
344
- children: block.detail
345
- })]
346
- })
364
+ children: block.tooltip === void 0 ? surface : /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: surface }), /* @__PURE__ */ jsx(TooltipPopup, {
365
+ className: "max-w-72",
366
+ children: block.tooltip
367
+ })] })
347
368
  });
348
369
  };
349
370
  const MIXED_STRIPES = "repeating-linear-gradient(135deg, var(--muted) 0 6px, transparent 6px 12px)";
371
+ /** A suggestion's tint is half an entry's, so proposed time reads lighter. */
372
+ const TONE_TINT_PERCENT = {
373
+ entry: 16,
374
+ suggestion: 8
375
+ };
350
376
  function blockSurfaceStyle(block) {
351
377
  const tone = block.tone;
352
378
  const stripes = block.kind === "mixed" ? MIXED_STRIPES : void 0;
379
+ const suggestion = block.kind === "entry" && block.state === "suggestion";
353
380
  switch (tone.type) {
354
- case "neutral": return { backgroundImage: stripes };
381
+ case "neutral": return {
382
+ backgroundColor: suggestion ? "transparent" : void 0,
383
+ backgroundImage: stripes
384
+ };
355
385
  case "matter": {
356
386
  const color = resolveMatterColor(tone.matterId, tone.color);
387
+ const tint = suggestion ? TONE_TINT_PERCENT.suggestion : TONE_TINT_PERCENT.entry;
357
388
  return {
358
- backgroundColor: `color-mix(in srgb, ${color} 16%, var(--card))`,
389
+ backgroundColor: `color-mix(in srgb, ${color} ${String(tint)}%, var(--card))`,
359
390
  backgroundImage: stripes,
391
+ borderColor: suggestion ? color : void 0,
360
392
  borderInlineStartColor: color
361
393
  };
362
394
  }
@@ -1,6 +1,9 @@
1
1
  //#region src/calendar/day-calendar.logic.d.ts
2
2
  export declare const DAY_CALENDAR_GRANULARITIES: readonly [15, 30, 60];
3
3
  export type DayCalendarGranularity = (typeof DAY_CALENDAR_GRANULARITIES)[number];
4
+ /** Below this height a block cannot hold one line of its label, so it draws
5
+ * its surface only; its accessible label and tooltip still carry it. */
6
+ export declare const DAY_CALENDAR_ONE_LINE_BLOCK_MIN_PX = 16;
4
7
  /**
5
8
  * Height of one grid slot. Coarser grids get taller slots, but not
6
9
  * proportionally: an hour stays tall enough for a two-line block.
@@ -6,6 +6,9 @@ const DAY_CALENDAR_GRANULARITIES = [
6
6
  30,
7
7
  60
8
8
  ];
9
+ /** Below this height a block cannot hold one line of its label, so it draws
10
+ * its surface only; its accessible label and tooltip still carry it. */
11
+ const DAY_CALENDAR_ONE_LINE_BLOCK_MIN_PX = 16;
9
12
  /**
10
13
  * Height of one grid slot. Coarser grids get taller slots, but not
11
14
  * proportionally: an hour stays tall enough for a two-line block.
@@ -232,4 +235,4 @@ function compareIds(left, right) {
232
235
  return left > right ? 1 : 0;
233
236
  }
234
237
  //#endregion
235
- export { DAY_CALENDAR_GRANULARITIES, DAY_CALENDAR_SLOT_HEIGHT_PX, boundsHeight, clampToBounds, extendRange, getDayBounds, getHourTicks, granularityMs, isRepeatedWallTime, layoutOverlaps, mergeShortSegments, moveRange, offsetToTime, rangeFromDrag, slotAt, snapToGrid, timeToOffset };
238
+ export { DAY_CALENDAR_GRANULARITIES, DAY_CALENDAR_ONE_LINE_BLOCK_MIN_PX, DAY_CALENDAR_SLOT_HEIGHT_PX, boundsHeight, clampToBounds, extendRange, getDayBounds, getHourTicks, granularityMs, isRepeatedWallTime, layoutOverlaps, mergeShortSegments, moveRange, offsetToTime, rangeFromDrag, slotAt, snapToGrid, timeToOffset };
@@ -13,7 +13,7 @@ const ScrollArea = ({ axis = "both", className, children, scrollFade = false, sc
13
13
  children: [
14
14
  /* @__PURE__ */ jsx(ScrollArea$1.Viewport, {
15
15
  ref: viewportRef,
16
- className: cn("focus-visible:ring-ring focus-visible:ring-offset-background min-h-0 flex-1 overscroll-contain rounded-[inherit] outline-none focus-visible:ring-2 focus-visible:ring-offset-1 data-has-overflow-x:overscroll-x-contain", scrollFade && "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] [--fade-size:1.5rem]", scrollbarGutter && "data-has-overflow-x:pb-2.5 data-has-overflow-y:pe-2.5", AXIS_VIEWPORT_CLASS[axis]),
16
+ className: cn("focus-visible:ring-ring focus-visible:ring-offset-background relative min-h-0 flex-1 overscroll-contain rounded-[inherit] outline-none focus-visible:ring-2 focus-visible:ring-offset-1 data-has-overflow-x:overscroll-x-contain", scrollFade && "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] [--fade-size:1.5rem]", scrollbarGutter && "data-has-overflow-x:pb-2.5 data-has-overflow-y:pe-2.5", AXIS_VIEWPORT_CLASS[axis]),
17
17
  "data-slot": "scroll-area-viewport",
18
18
  children
19
19
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/ui",
3
- "version": "0.58.0",
3
+ "version": "0.59.1",
4
4
  "description": "Stella's design system: bidi-aware React primitives built on Base UI, the dockable inspector pane, and the Tailwind v4 theme they are styled with.",
5
5
  "keywords": [
6
6
  "base-ui",