@stll/ui 0.44.1 → 0.45.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 (38) hide show
  1. package/dist/components/accordion.d.ts +6 -2
  2. package/dist/components/accordion.js +4 -4
  3. package/dist/components/button.d.ts +2 -2
  4. package/dist/components/court-badge.d.ts +7 -1
  5. package/dist/components/court-badge.js +13 -3
  6. package/dist/components/date-picker-popover.d.ts +12 -2
  7. package/dist/components/date-picker-popover.js +105 -9
  8. package/dist/components/date-range-filter.d.ts +14 -0
  9. package/dist/components/date-range-filter.js +74 -0
  10. package/dist/components/document-identity-badge.d.ts +12 -0
  11. package/dist/components/document-identity-badge.js +94 -0
  12. package/dist/components/document-identity-badge.logic.d.ts +25 -0
  13. package/dist/components/document-identity-badge.logic.js +25 -0
  14. package/dist/components/landing.d.ts +1 -1
  15. package/dist/components/landing.js +1 -1
  16. package/dist/components/matter-icon.d.ts +16 -0
  17. package/dist/components/matter-icon.js +56 -0
  18. package/dist/components/menu.d.ts +9 -1
  19. package/dist/components/menu.js +6 -6
  20. package/dist/components/outline-rail.d.ts +8 -1
  21. package/dist/components/outline-rail.js +95 -48
  22. package/dist/components/search-field.js +30 -34
  23. package/dist/components/sheet.js +3 -2
  24. package/dist/components/sidebar.d.ts +1 -1
  25. package/dist/components/switch.d.ts +5 -0
  26. package/dist/components/switch.js +16 -0
  27. package/dist/components/toast.js +39 -23
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +2 -2
  30. package/dist/inspector/entity-tab.js +1 -2
  31. package/dist/lib/control-size.d.ts +1 -1
  32. package/dist/lib/matter-colors.d.ts +38 -0
  33. package/dist/lib/matter-colors.js +65 -0
  34. package/dist/lib/panel-inset.d.ts +11 -0
  35. package/dist/lib/panel-inset.js +33 -0
  36. package/dist/review/review-word-diff.d.ts +13 -0
  37. package/dist/review/review-word-diff.js +78 -0
  38. package/package.json +37 -5
@@ -2,7 +2,11 @@ import { Accordion as Accordion$1 } from "@base-ui/react/accordion";
2
2
  //#region src/components/accordion.d.ts
3
3
  declare const Accordion: <Value = unknown>(props: Accordion$1.Root.Props<Value>) => import("react").JSX.Element;
4
4
  declare const AccordionItem: ({ className, ...props }: Accordion$1.Item.Props) => import("react").JSX.Element;
5
- declare const AccordionTrigger: ({ className, children, ...props }: Accordion$1.Trigger.Props) => import("react").JSX.Element;
6
- declare const AccordionPanel: ({ className, children, ...props }: Accordion$1.Panel.Props) => import("react").JSX.Element;
5
+ declare const AccordionTrigger: ({ className, children, size, ...props }: Accordion$1.Trigger.Props & {
6
+ size?: "default" | "compact";
7
+ }) => import("react").JSX.Element;
8
+ declare const AccordionPanel: ({ className, children, size, ...props }: Accordion$1.Panel.Props & {
9
+ size?: "default" | "compact";
10
+ }) => import("react").JSX.Element;
7
11
  //#endregion
8
12
  export { Accordion, AccordionPanel as AccordionContent, AccordionPanel, AccordionItem, AccordionTrigger };
@@ -13,10 +13,10 @@ const AccordionItem = ({ className, ...props }) => /* @__PURE__ */ jsx(Accordion
13
13
  "data-slot": "accordion-item",
14
14
  ...props
15
15
  });
16
- const AccordionTrigger = ({ className, children, ...props }) => /* @__PURE__ */ jsx(Accordion$1.Header, {
16
+ const AccordionTrigger = ({ className, children, size = "default", ...props }) => /* @__PURE__ */ jsx(Accordion$1.Header, {
17
17
  className: "flex",
18
18
  children: /* @__PURE__ */ jsxs(Accordion$1.Trigger, {
19
- className: cn("focus-visible:ring-ring flex flex-1 cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-start text-sm font-medium outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-64 data-panel-open:*:data-[slot=accordion-indicator]:rotate-180", className),
19
+ className: cn("focus-visible:ring-ring flex flex-1 cursor-pointer items-start justify-between gap-4 rounded-md text-start font-medium outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-64 data-panel-open:*:data-[slot=accordion-indicator]:rotate-180", size === "compact" ? "py-2 text-xs" : "py-4 text-sm", className),
20
20
  "data-slot": "accordion-trigger",
21
21
  ...props,
22
22
  children: [children, /* @__PURE__ */ jsx(ChevronDownIcon, {
@@ -25,12 +25,12 @@ const AccordionTrigger = ({ className, children, ...props }) => /* @__PURE__ */
25
25
  })]
26
26
  })
27
27
  });
28
- const AccordionPanel = ({ className, children, ...props }) => /* @__PURE__ */ jsx(Accordion$1.Panel, {
28
+ const AccordionPanel = ({ className, children, size = "default", ...props }) => /* @__PURE__ */ jsx(Accordion$1.Panel, {
29
29
  className: "text-muted-foreground h-(--accordion-panel-height) overflow-hidden text-sm transition-[height] duration-200 ease-in-out data-ending-style:h-0 data-starting-style:h-0",
30
30
  "data-slot": "accordion-panel",
31
31
  ...props,
32
32
  children: /* @__PURE__ */ jsx("div", {
33
- className: cn("pt-0 pb-4", className),
33
+ className: cn("pt-0", size === "compact" ? "pb-3" : "pb-4", className),
34
34
  children
35
35
  })
36
36
  });
@@ -13,8 +13,8 @@ import { VariantProps } from "class-variance-authority";
13
13
  */
14
14
  declare const buttonAccessibleDisabledClass = "cursor-not-allowed opacity-64";
15
15
  declare const buttonVariants: (props?: ({
16
- size?: "icon" | "sm" | "default" | "lg" | "row" | "chip" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "xl" | "xs" | null | undefined;
17
- variant?: "link" | "destructive" | "muted" | "secondary" | "default" | "destructive-outline" | "destructive-ghost" | "ghost" | "outline" | null | undefined;
16
+ size?: "default" | "chip" | "icon" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "row" | "sm" | "xl" | "xs" | null | undefined;
17
+ variant?: "link" | "default" | "destructive" | "destructive-outline" | "destructive-ghost" | "ghost" | "muted" | "outline" | "secondary" | null | undefined;
18
18
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
19
19
  type ButtonProps = {
20
20
  variant?: VariantProps<typeof buttonVariants>["variant"];
@@ -9,10 +9,16 @@
9
9
  * carries meaning on its own anyway: the court's name stands beside it.
10
10
  */
11
11
  export type CourtBadgeWeight = "solid" | "tinted" | "outline" | "dashed";
12
+ /**
13
+ * `default` stands on its own beside a court's name; `inline` sits inside a
14
+ * line of small text (a list row's meta line) without making it taller.
15
+ */
16
+ export type CourtBadgeSize = "default" | "inline";
12
17
  type CourtBadgeProps = {
13
18
  /** The court's own abbreviation, in the language its name is in. */
14
19
  abbreviation: string;
15
20
  weight: CourtBadgeWeight;
21
+ size?: CourtBadgeSize | undefined;
16
22
  className?: string;
17
23
  };
18
24
  /**
@@ -23,5 +29,5 @@ type CourtBadgeProps = {
23
29
  * abbreviation is actually known. There is no placeholder: a court nobody
24
30
  * abbreviates gets no chip.
25
31
  */
26
- export declare const CourtBadge: ({ abbreviation, className, weight }: CourtBadgeProps) => import("react").JSX.Element;
32
+ export declare const CourtBadge: ({ abbreviation, className, size, weight }: CourtBadgeProps) => import("react").JSX.Element;
27
33
  //#endregion
@@ -1,7 +1,17 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  //#region src/components/court-badge.tsx
4
- const COURT_BADGE_BASE_CLASS = "inline-flex shrink-0 items-center rounded-sm border px-1 py-0.5 font-mono text-xs leading-4 font-semibold tracking-wide whitespace-nowrap";
4
+ const COURT_BADGE_BASE_CLASS = "inline-flex shrink-0 items-center rounded-sm border font-mono font-semibold tracking-wide whitespace-nowrap";
5
+ /**
6
+ * The line box runs a third taller than the type, because these capitals
7
+ * carry diacritics (Ú, Ř, Š) whose ink rises above cap height; a tighter box
8
+ * sets the acute against the pill's border. `inline` keeps that ratio at a
9
+ * smaller size and drops the padding, so the chip fits a 16px text line.
10
+ */
11
+ const SIZE_CLASS = {
12
+ default: "px-1 py-0.5 text-xs leading-4",
13
+ inline: "px-0.5 text-3xs leading-3.5"
14
+ };
5
15
  /**
6
16
  * Every weight keeps a border, because `--muted` is a 4% tint: a borderless
7
17
  * tinted chip on a sidebar or a muted row is a pill whose edge nobody can
@@ -31,8 +41,8 @@ const WEIGHT_CLASS = {
31
41
  * abbreviation is actually known. There is no placeholder: a court nobody
32
42
  * abbreviates gets no chip.
33
43
  */
34
- const CourtBadge = ({ abbreviation, className, weight }) => /* @__PURE__ */ jsx("span", {
35
- className: cn(COURT_BADGE_BASE_CLASS, WEIGHT_CLASS[weight], className),
44
+ const CourtBadge = ({ abbreviation, className, size = "default", weight }) => /* @__PURE__ */ jsx("span", {
45
+ className: cn(COURT_BADGE_BASE_CLASS, SIZE_CLASS[size], WEIGHT_CLASS[weight], className),
36
46
  "data-slot": "court-badge",
37
47
  dir: "ltr",
38
48
  children: abbreviation
@@ -41,6 +41,16 @@ type DatePickerPopoverProps = {
41
41
  placeholderLabel?: string;
42
42
  clearLabel?: string;
43
43
  defaultOpen?: boolean;
44
+ open?: boolean;
45
+ triggerRef?: React.Ref<HTMLButtonElement> | undefined;
46
+ /** Keep the trigger focused when opening a following range bound. */
47
+ focusTriggerOnOpen?: boolean;
48
+ /** Enables direct date entry, with host-localized validation. */
49
+ dateInputLabel?: string;
50
+ outOfRangeLabel?: string;
51
+ noFuture?: boolean;
52
+ /** Host-owned calendar date; defaults to the browser timezone. */
53
+ getToday?: () => string;
44
54
  onOpenChange?: (open: boolean) => void;
45
55
  /** Label for the "go to today" button. Auto-localized from the locale when omitted. */
46
56
  todayLabel?: string;
@@ -57,8 +67,8 @@ type DatePickerPopoverProps = {
57
67
  /** Label of the time input in `date-time` mode. */
58
68
  timeLabel?: string;
59
69
  /** Inclusive `YYYY-MM-DD` bounds; in `date-time` mode they bound the day. */
60
- minDate?: string;
61
- maxDate?: string;
70
+ minDate?: string | undefined;
71
+ maxDate?: string | undefined;
62
72
  isDateDisabled?: (date: string) => boolean;
63
73
  layer?: OverlayLayer;
64
74
  };
@@ -3,6 +3,7 @@ import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "../icons.js";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { Button } from "./button.js";
5
5
  import { DirectionalIcon } from "./directional-icon.js";
6
+ import { Input } from "./input.js";
6
7
  import { FIELD_TRIGGER_CLASS_NAME } from "../lib/field-trigger.js";
7
8
  import { getLocaleWeekInfo, getWeekendDays } from "../lib/week.js";
8
9
  import { DATE_PICKER_MODE, formatDateTimeValue, getHourOptions, getMinuteOptions, getTimeFieldNames, joinPickerTime, localDateFromTimestamp, millisecondsUntilNextLocalDate, parseDateTimeValue, resolveCalendarViewMonth, shiftCalendarDate, splitPickerTime } from "./date-picker-popover.logic.js";
@@ -10,7 +11,7 @@ import { Popover, PopoverContent as PopoverPopup, PopoverTrigger } from "./popov
10
11
  import { Select, SelectContent as SelectPopup, SelectItem, SelectTrigger, SelectValue } from "./select.js";
11
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
13
  import { useCallback, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
13
- import { panic } from "better-result";
14
+ import { Result, panic } from "better-result";
14
15
  import { Temporal } from "temporal-polyfill/full";
15
16
  //#region src/components/date-picker-popover.tsx
16
17
  const toISODate = (date) => date.toString();
@@ -57,7 +58,7 @@ const subscribeToLocalDate = (onStoreChange) => {
57
58
  if (localDateListeners.size === 0) stopLocalDateSubscription();
58
59
  };
59
60
  };
60
- const useHydrationSafeToday = () => useSyncExternalStore(subscribeToLocalDate, getLocalToday, () => HYDRATION_DATE);
61
+ const useHydrationSafeToday = (getToday) => useSyncExternalStore(subscribeToLocalDate, getToday, () => HYDRATION_DATE);
61
62
  const useHydrationSafeBrowserLocale = () => useSyncExternalStore(noopSubscribe, () => navigator.language || HYDRATION_LOCALE, () => HYDRATION_LOCALE);
62
63
  const getFirstDayOfWeek = (locale) => {
63
64
  const info = getLocaleWeekInfo(locale);
@@ -252,7 +253,7 @@ const PICKER_SIZE_CLASS_NAMES = {
252
253
  }
253
254
  };
254
255
  const DatePickerPopoverContent = (props) => {
255
- const { id, labelledBy, className, disabled, hideClear, size = "compact", variant = "inline", mode = DATE_PICKER_MODE.date, value: rawValue, onChange, locale, isOverdue = false, showIcon = true, placeholderLabel, clearLabel = "Clear date", defaultOpen = false, onOpenChange, todayLabel: todayLabelProp, overdueLabel, minDate, maxDate, isDateDisabled, layer = "default", today } = props;
256
+ const { id, labelledBy, className, disabled, hideClear, size = "compact", variant = "inline", mode = DATE_PICKER_MODE.date, value: rawValue, onChange, locale, isOverdue = false, showIcon = true, placeholderLabel, clearLabel = "Clear date", defaultOpen = false, open: controlledOpen, triggerRef, focusTriggerOnOpen = false, dateInputLabel, outOfRangeLabel, onOpenChange, todayLabel: todayLabelProp, overdueLabel, minDate, maxDate, isDateDisabled, layer = "default", today } = props;
256
257
  const selection = resolveSelection(mode, rawValue);
257
258
  const value = selection.date;
258
259
  const labels = resolvePopupLabels(props);
@@ -294,14 +295,19 @@ const DatePickerPopoverContent = (props) => {
294
295
  maxDate,
295
296
  isDateDisabled
296
297
  ]);
298
+ const dateDraft = useDatePickerDraft({
299
+ value,
300
+ isDayDisabled,
301
+ onChange
302
+ });
297
303
  const displayLabel = formatSelection(locale, selection) ?? placeholderLabel ?? "—";
298
304
  const selectDay = (date) => {
299
305
  switch (selection.mode) {
300
306
  case DATE_PICKER_MODE.date:
301
- onChange(date);
307
+ dateDraft.apply(date);
302
308
  return;
303
309
  case DATE_PICKER_MODE.dateTime:
304
- onChange(formatDateTimeValue({
310
+ dateDraft.apply(formatDateTimeValue({
305
311
  date,
306
312
  time: selection.time ?? "00:00"
307
313
  }));
@@ -311,7 +317,7 @@ const DatePickerPopoverContent = (props) => {
311
317
  };
312
318
  const handleTimeChange = (clock) => {
313
319
  if (value === "") return;
314
- onChange(formatDateTimeValue({
320
+ dateDraft.apply(formatDateTimeValue({
315
321
  date: value,
316
322
  time: joinPickerTime(clock)
317
323
  }));
@@ -443,6 +449,7 @@ const DatePickerPopoverContent = (props) => {
443
449
  })();
444
450
  return /* @__PURE__ */ jsxs(Popover, {
445
451
  defaultOpen,
452
+ open: controlledOpen,
446
453
  onOpenChange: handleOpenChange,
447
454
  children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
448
455
  disabled,
@@ -451,6 +458,7 @@ const DatePickerPopoverContent = (props) => {
451
458
  "aria-labelledby": labelledBy ? `${labelledBy} ${displayValueId}` : void 0,
452
459
  className: cn(TRIGGER_VARIANT_CLASS_NAMES[variant], triggerToneClassName, className),
453
460
  id,
461
+ ref: triggerRef,
454
462
  type: "button"
455
463
  }),
456
464
  children: [
@@ -466,7 +474,8 @@ const DatePickerPopoverContent = (props) => {
466
474
  })
467
475
  ]
468
476
  }), /* @__PURE__ */ jsx(PopoverPopup, {
469
- initialFocus: () => gridRef.current?.querySelector(`[data-date="${value || today}"]`) ?? gridRef.current,
477
+ onBlur: dateDraft.handleBlur,
478
+ initialFocus: focusTriggerOnOpen ? false : () => gridRef.current?.querySelector(`[data-date="${value || today}"]`) ?? gridRef.current,
470
479
  className: "data-starting-style:scale-100",
471
480
  layer,
472
481
  padding: "sm",
@@ -516,6 +525,7 @@ const DatePickerPopoverContent = (props) => {
516
525
  onClick: () => {
517
526
  if (!isUnavailable) selectDay(day.date);
518
527
  },
528
+ onFocus: () => setFocusedDate(day.date),
519
529
  role: "gridcell",
520
530
  tabIndex: isFocused ? 0 : -1,
521
531
  type: "button",
@@ -546,11 +556,21 @@ const DatePickerPopoverContent = (props) => {
546
556
  onChange: handleTimeChange,
547
557
  time: selection.time
548
558
  }),
559
+ /* @__PURE__ */ jsx(TypedDateField, {
560
+ mode: selection.mode,
561
+ label: dateInputLabel,
562
+ value: dateDraft.value,
563
+ invalid: dateDraft.invalid,
564
+ errorLabel: outOfRangeLabel,
565
+ onDraftChange: dateDraft.edit,
566
+ onCommit: dateDraft.submit
567
+ }),
549
568
  /* @__PURE__ */ jsxs("div", {
550
569
  className: "mt-1 flex items-center gap-1 border-t pt-1",
551
570
  children: [/* @__PURE__ */ jsx(Button, {
552
571
  className: "flex-1",
553
572
  onClick: () => {
573
+ dateDraft.discard();
554
574
  const todayDate = Temporal.PlainDate.from(today);
555
575
  setViewMonthOverride({
556
576
  month: todayDate.month - 1,
@@ -563,7 +583,7 @@ const DatePickerPopoverContent = (props) => {
563
583
  children: todayLabel
564
584
  }), value && !hideClear && /* @__PURE__ */ jsx(Button, {
565
585
  className: "flex-1",
566
- onClick: () => onChange(null),
586
+ onClick: () => dateDraft.apply(null),
567
587
  size: "xs",
568
588
  variant: "ghost",
569
589
  children: clearLabel
@@ -576,10 +596,12 @@ const DatePickerPopoverContent = (props) => {
576
596
  };
577
597
  const DatePickerPopover = (props) => {
578
598
  const browserLocale = useHydrationSafeBrowserLocale();
579
- const today = useHydrationSafeToday();
599
+ const today = useHydrationSafeToday(props.getToday ?? getLocalToday);
580
600
  const locale = props.locale ?? browserLocale;
601
+ const maxDate = props.noFuture && (!props.maxDate || props.maxDate > today) ? today : props.maxDate;
581
602
  return /* @__PURE__ */ jsx(DatePickerPopoverContent, {
582
603
  ...props,
604
+ maxDate,
583
605
  locale,
584
606
  today
585
607
  });
@@ -738,5 +760,79 @@ const YearGrid = ({ decadeBase, currentYear, cellClassName, onSelect, today }) =
738
760
  }, row[0]))
739
761
  });
740
762
  };
763
+ const useDatePickerDraft = ({ value, isDayDisabled, onChange }) => {
764
+ const [draft, setDraft] = useState(null);
765
+ const commitStatus = useRef("consumed");
766
+ const hasDraft = draft?.baseValue === value;
767
+ const inputValue = hasDraft ? draft.value : value;
768
+ const parsed = Result.try(() => Temporal.PlainDate.from(inputValue));
769
+ const invalid = hasDraft && inputValue !== "" && (parsed.isErr() || parsed.value.toString() !== inputValue || isDayDisabled(inputValue));
770
+ const discard = () => {
771
+ commitStatus.current = "consumed";
772
+ setDraft(null);
773
+ };
774
+ const apply = (next) => {
775
+ discard();
776
+ onChange(next);
777
+ };
778
+ const commit = () => {
779
+ if (commitStatus.current !== "pending" || invalid || inputValue === value) return;
780
+ apply(inputValue || null);
781
+ };
782
+ return {
783
+ value: inputValue,
784
+ invalid,
785
+ edit: (next) => {
786
+ commitStatus.current = "pending";
787
+ setDraft({
788
+ baseValue: value,
789
+ value: next
790
+ });
791
+ },
792
+ discard,
793
+ apply,
794
+ submit: () => {
795
+ if (!invalid) apply(inputValue || null);
796
+ },
797
+ handleBlur: (event) => {
798
+ if (event.currentTarget.contains(event.relatedTarget)) return;
799
+ commit();
800
+ }
801
+ };
802
+ };
803
+ const TypedDateField = ({ mode, label, value, invalid, errorLabel = "Choose a date within the allowed range.", onDraftChange, onCommit }) => {
804
+ const id = useId();
805
+ if (!label || mode !== DATE_PICKER_MODE.date) return null;
806
+ return /* @__PURE__ */ jsxs("div", {
807
+ className: "mt-2 flex flex-col gap-1",
808
+ children: [
809
+ /* @__PURE__ */ jsx("label", {
810
+ htmlFor: id,
811
+ className: "text-xs",
812
+ children: label
813
+ }),
814
+ /* @__PURE__ */ jsx(Input, {
815
+ id,
816
+ value,
817
+ placeholder: "YYYY-MM-DD",
818
+ dir: "ltr",
819
+ "aria-invalid": invalid,
820
+ "aria-describedby": invalid ? `${id}-error` : void 0,
821
+ onChange: (event) => onDraftChange(event.currentTarget.value),
822
+ onKeyDown: (event) => {
823
+ if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
824
+ event.preventDefault();
825
+ onCommit();
826
+ }
827
+ }),
828
+ invalid && /* @__PURE__ */ jsx("p", {
829
+ id: `${id}-error`,
830
+ role: "alert",
831
+ className: "text-destructive text-xs",
832
+ children: errorLabel
833
+ })
834
+ ]
835
+ });
836
+ };
741
837
  //#endregion
742
838
  export { DatePickerPopover };
@@ -0,0 +1,14 @@
1
+ import { DatePickerPopoverProps } from "./date-picker-popover.js";
2
+ //#region src/components/date-range-filter.d.ts
3
+ type DateRangeFilterProps = Omit<DatePickerPopoverProps, "value" | "onChange" | "minDate" | "open" | "defaultOpen" | "onOpenChange" | "triggerRef" | "focusTriggerOnOpen" | "id" | "labelledBy" | "mode"> & {
4
+ from: string | null;
5
+ to: string | null;
6
+ onFromChange: (value: string | null) => void;
7
+ onToChange: (value: string | null) => void;
8
+ fromLabel: string;
9
+ toLabel: string;
10
+ };
11
+ /** Both calendar bounds and the focus handoff belong to the range. */
12
+ declare const DateRangeFilter: ({ from, to, onFromChange, onToChange, fromLabel, toLabel, className, maxDate, ...pickerProps }: DateRangeFilterProps) => import("react").JSX.Element;
13
+ //#endregion
14
+ export { DateRangeFilter, type DateRangeFilterProps };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { DatePickerPopover } from "./date-picker-popover.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { useId, useRef, useState } from "react";
6
+ //#region src/components/date-range-filter.tsx
7
+ /** Both calendar bounds and the focus handoff belong to the range. */
8
+ const DateRangeFilter = ({ from, to, onFromChange, onToChange, fromLabel, toLabel, className, maxDate, ...pickerProps }) => {
9
+ const id = useId();
10
+ const toRef = useRef(null);
11
+ const [picker, setPicker] = useState({ type: "closed" });
12
+ const fromMax = to && (!maxDate || to < maxDate) ? to : maxDate;
13
+ return /* @__PURE__ */ jsxs("div", {
14
+ className: cn("flex flex-col gap-1.5", className),
15
+ children: [/* @__PURE__ */ jsxs("div", {
16
+ className: "flex min-w-0 flex-col gap-1.5",
17
+ children: [/* @__PURE__ */ jsx("span", {
18
+ id: `${id}-from-label`,
19
+ className: "text-muted-foreground text-xs font-medium",
20
+ children: fromLabel
21
+ }), /* @__PURE__ */ jsx(DatePickerPopover, {
22
+ ...pickerProps,
23
+ mode: "date",
24
+ id: `${id}-from`,
25
+ defaultOpen: false,
26
+ labelledBy: `${id}-from-label`,
27
+ dateInputLabel: fromLabel,
28
+ value: from,
29
+ maxDate: fromMax,
30
+ minDate: void 0,
31
+ triggerRef: void 0,
32
+ focusTriggerOnOpen: false,
33
+ open: picker.type === "from",
34
+ onOpenChange: (open) => setPicker(open ? { type: "from" } : { type: "closed" }),
35
+ onChange: (next) => {
36
+ onFromChange(next);
37
+ if (!next) return;
38
+ setPicker({
39
+ type: "to",
40
+ focus: "trigger"
41
+ });
42
+ toRef.current?.focus();
43
+ }
44
+ })]
45
+ }), /* @__PURE__ */ jsxs("div", {
46
+ className: "flex min-w-0 flex-col gap-1.5",
47
+ children: [/* @__PURE__ */ jsx("span", {
48
+ id: `${id}-to-label`,
49
+ className: "text-muted-foreground text-xs font-medium",
50
+ children: toLabel
51
+ }), /* @__PURE__ */ jsx(DatePickerPopover, {
52
+ ...pickerProps,
53
+ mode: "date",
54
+ id: `${id}-to`,
55
+ defaultOpen: false,
56
+ labelledBy: `${id}-to-label`,
57
+ dateInputLabel: toLabel,
58
+ value: to,
59
+ minDate: from ?? void 0,
60
+ maxDate,
61
+ open: picker.type === "to",
62
+ triggerRef: toRef,
63
+ focusTriggerOnOpen: picker.type === "to" && picker.focus === "trigger",
64
+ onOpenChange: (open) => setPicker(open ? {
65
+ type: "to",
66
+ focus: "calendar"
67
+ } : { type: "closed" }),
68
+ onChange: onToChange
69
+ })]
70
+ })]
71
+ });
72
+ };
73
+ //#endregion
74
+ export { DateRangeFilter };
@@ -0,0 +1,12 @@
1
+ import { CourtBadgeSize } from "./court-badge.js";
2
+ import { DocumentIdentity } from "./document-identity-badge.logic.js";
3
+ //#region src/components/document-identity-badge.d.ts
4
+ export declare const DocumentIdentityBadge: ({ identity, size, title }: DocumentIdentityBadgeProps) => import("react").JSX.Element;
5
+ type DocumentIdentityBadgeProps = {
6
+ identity: DocumentIdentity;
7
+ /** Decision chips can fit an inline metadata row. */
8
+ size?: CourtBadgeSize | undefined;
9
+ /** Omit when the enclosing tab already supplies the full-title tooltip. */
10
+ title?: string | undefined;
11
+ };
12
+ //#endregion
@@ -0,0 +1,94 @@
1
+ import { FileTextIcon } from "../icons.js";
2
+ import { cn } from "../lib/utils.js";
3
+ import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./tooltip.js";
4
+ import { CourtBadge } from "./court-badge.js";
5
+ import { COURT_TIER_WEIGHT, statuteIdentityLabels } from "./document-identity-badge.logic.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { panic } from "better-result";
8
+ //#region src/components/document-identity-badge.tsx
9
+ const DocumentIdentityBadge = ({ identity, size, title }) => {
10
+ const content = documentIdentityMark(identity, size);
11
+ if (title === void 0) return content;
12
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
13
+ render: /* @__PURE__ */ jsx("span", { className: "inline-flex max-w-full min-w-0" }),
14
+ children: content
15
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: title })] });
16
+ };
17
+ function documentIdentityMark(identity, size) {
18
+ switch (identity.kind) {
19
+ case "statute": {
20
+ const labels = statuteIdentityLabels(identity);
21
+ if (labels === null) return unknownDocumentMark();
22
+ return /* @__PURE__ */ jsxs("span", {
23
+ className: cn("@container/document-identity inline-flex max-w-full shrink-0 items-center justify-center font-mono leading-4 font-semibold tabular-nums", labels.short.length > 7 ? "text-[9px]" : "text-[10px]"),
24
+ style: { width: `${labels.long.length + .25}ch` },
25
+ "data-slot": "document-identity-badge",
26
+ "data-kind": "statute",
27
+ dir: "ltr",
28
+ children: [/* @__PURE__ */ jsx("bdi", {
29
+ className: statuteLabelClasses(labels.long.length).short,
30
+ children: labels.short
31
+ }), /* @__PURE__ */ jsx("bdi", {
32
+ className: statuteLabelClasses(labels.long.length).long,
33
+ children: labels.long
34
+ })]
35
+ });
36
+ }
37
+ case "decision": {
38
+ const abbreviation = identity.courtAbbreviation?.trim();
39
+ if (!abbreviation) return unknownDocumentMark();
40
+ return /* @__PURE__ */ jsx("span", {
41
+ className: "inline-flex max-w-full min-w-0",
42
+ "data-slot": "document-identity-badge",
43
+ "data-kind": "decision",
44
+ children: /* @__PURE__ */ jsx(CourtBadge, {
45
+ abbreviation,
46
+ size,
47
+ weight: identity.courtTier === void 0 ? "outline" : COURT_TIER_WEIGHT[identity.courtTier],
48
+ className: cn("max-w-full overflow-hidden", abbreviation.length > 4 && "px-0.5 text-[9px] tracking-normal")
49
+ })
50
+ });
51
+ }
52
+ case "unknown": return unknownDocumentMark();
53
+ default: return panic("Unhandled document identity");
54
+ }
55
+ }
56
+ function unknownDocumentMark() {
57
+ return /* @__PURE__ */ jsx(FileTextIcon, {
58
+ "aria-hidden": "true",
59
+ className: "size-3.5 shrink-0",
60
+ "data-slot": "document-identity-badge",
61
+ "data-kind": "unknown"
62
+ });
63
+ }
64
+ const STATUTE_LABEL_CLASSES = {
65
+ 6: {
66
+ short: "@[6ch]/document-identity:hidden",
67
+ long: "hidden @[6ch]/document-identity:inline"
68
+ },
69
+ 7: {
70
+ short: "@[7ch]/document-identity:hidden",
71
+ long: "hidden @[7ch]/document-identity:inline"
72
+ },
73
+ 8: {
74
+ short: "@[8ch]/document-identity:hidden",
75
+ long: "hidden @[8ch]/document-identity:inline"
76
+ },
77
+ 9: {
78
+ short: "@[9ch]/document-identity:hidden",
79
+ long: "hidden @[9ch]/document-identity:inline"
80
+ },
81
+ 10: {
82
+ short: "@[10ch]/document-identity:hidden",
83
+ long: "hidden @[10ch]/document-identity:inline"
84
+ }
85
+ };
86
+ function statuteLabelClasses(length) {
87
+ if (length <= 6) return STATUTE_LABEL_CLASSES[6];
88
+ if (length === 7) return STATUTE_LABEL_CLASSES[7];
89
+ if (length === 8) return STATUTE_LABEL_CLASSES[8];
90
+ if (length === 9) return STATUTE_LABEL_CLASSES[9];
91
+ return STATUTE_LABEL_CLASSES[10];
92
+ }
93
+ //#endregion
94
+ export { DocumentIdentityBadge };
@@ -0,0 +1,25 @@
1
+ //#region src/components/document-identity-badge.logic.d.ts
2
+ export declare const COURT_TIER_WEIGHT: {
3
+ readonly constitutional: "solid";
4
+ readonly supreme: "tinted";
5
+ readonly regional: "outline";
6
+ readonly other: "dashed";
7
+ };
8
+ export type DocumentIdentity = {
9
+ kind: "statute";
10
+ number: string | null;
11
+ year: string | null;
12
+ } | {
13
+ kind: "decision";
14
+ courtAbbreviation: string | null | undefined;
15
+ courtTier?: keyof typeof COURT_TIER_WEIGHT | undefined;
16
+ } | {
17
+ kind: "unknown";
18
+ };
19
+ export declare const statuteIdentityLabels: ({ number, year }: Extract<DocumentIdentity, {
20
+ kind: "statute";
21
+ }>) => {
22
+ short: string;
23
+ long: string;
24
+ } | null;
25
+ //#endregion
@@ -0,0 +1,25 @@
1
+ //#region src/components/document-identity-badge.logic.ts
2
+ const COURT_TIER_WEIGHT = {
3
+ constitutional: "solid",
4
+ supreme: "tinted",
5
+ regional: "outline",
6
+ other: "dashed"
7
+ };
8
+ const statuteIdentityLabels = ({ number, year }) => {
9
+ const ordinal = number?.trim() || null;
10
+ const fullYear = year?.trim() || null;
11
+ if (ordinal === null) return fullYear === null ? null : {
12
+ short: fullYear,
13
+ long: fullYear
14
+ };
15
+ if (fullYear === null) return {
16
+ short: ordinal,
17
+ long: ordinal
18
+ };
19
+ return {
20
+ short: `${ordinal}/${fullYear.slice(-2)}`,
21
+ long: `${ordinal}/${fullYear}`
22
+ };
23
+ };
24
+ //#endregion
25
+ export { COURT_TIER_WEIGHT, statuteIdentityLabels };
@@ -25,7 +25,7 @@ type LandingGreetingProps = {
25
25
  };
26
26
  export declare const LandingGreeting: ({ icon, children }: LandingGreetingProps) => import("react").JSX.Element;
27
27
  /** A column's heading: an icon and a sentence-case label, as a link or a trigger. */
28
- export declare const LANDING_SECTION_HEADING_CLASS = "text-muted-foreground hover:text-foreground focus-visible:ring-ring flex items-center gap-2 rounded-md px-2 text-sm font-medium outline-none focus-visible:ring-2";
28
+ export declare const LANDING_SECTION_HEADING_CLASS = "text-foreground hover:bg-accent/50 focus-visible:ring-ring [&>svg]:text-muted-foreground flex items-center gap-2 rounded-md px-2 py-0.5 text-sm font-semibold outline-none focus-visible:ring-2";
29
29
  /** A row of a column, as a link or a button. Its text lines up with the
30
30
  * heading's label; the icon slot under the heading icon stays empty unless
31
31
  * the icon identifies the row (a matter's colour). */
@@ -40,7 +40,7 @@ const LandingGreeting = ({ icon, children }) => /* @__PURE__ */ jsxs("div", {
40
40
  })]
41
41
  });
42
42
  /** A column's heading: an icon and a sentence-case label, as a link or a trigger. */
43
- const LANDING_SECTION_HEADING_CLASS = "text-muted-foreground hover:text-foreground focus-visible:ring-ring flex items-center gap-2 rounded-md px-2 text-sm font-medium outline-none focus-visible:ring-2";
43
+ const LANDING_SECTION_HEADING_CLASS = "text-foreground hover:bg-accent/50 focus-visible:ring-ring [&>svg]:text-muted-foreground flex items-center gap-2 rounded-md px-2 py-0.5 text-sm font-semibold outline-none focus-visible:ring-2";
44
44
  /** A row of a column, as a link or a button. Its text lines up with the
45
45
  * heading's label; the icon slot under the heading icon stays empty unless
46
46
  * the icon identifies the row (a matter's colour). */
@@ -0,0 +1,16 @@
1
+ //#region src/components/matter-icon.d.ts
2
+ type MatterIconProps = {
3
+ className?: string | undefined;
4
+ } & ({
5
+ matter: {
6
+ id: string;
7
+ color: string | null;
8
+ };
9
+ } | {
10
+ variant: "none" | "all";
11
+ });
12
+ export declare const MatterIcon: (props: MatterIconProps) => import("react").JSX.Element;
13
+ export declare const MattersNavIcon: ({ className }: {
14
+ className?: string | undefined;
15
+ }) => import("react").JSX.Element;
16
+ //#endregion