@boyernick/standard-ui-react 0.1.1-canary.2 → 0.1.1-canary.22

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 (56) hide show
  1. package/package.json +5 -3
  2. package/src/accordion.tsx +1 -1
  3. package/src/alert-dialog.tsx +1 -1
  4. package/src/autocomplete.tsx +12 -11
  5. package/src/badge.tsx +73 -9
  6. package/src/brand.tsx +2 -2
  7. package/src/breadcrumb.tsx +38 -20
  8. package/src/button.tsx +2 -2
  9. package/src/calendar.tsx +22 -11
  10. package/src/card.tsx +57 -14
  11. package/src/carousel.tsx +163 -6
  12. package/src/checkbox.tsx +35 -13
  13. package/src/collapsible.tsx +4 -2
  14. package/src/combobox.tsx +13 -12
  15. package/src/command.tsx +38 -14
  16. package/src/context-menu.tsx +6 -3
  17. package/src/date-picker.tsx +174 -0
  18. package/src/dialog.tsx +33 -10
  19. package/src/drawer.tsx +1 -1
  20. package/src/empty.tsx +4 -4
  21. package/src/field.tsx +1 -1
  22. package/src/filter-group.tsx +139 -0
  23. package/src/icons.tsx +39 -0
  24. package/src/illustrations.tsx +219 -141
  25. package/src/image-modal.tsx +440 -49
  26. package/src/index.ts +188 -0
  27. package/src/input.tsx +1 -1
  28. package/src/kbd.tsx +49 -0
  29. package/src/lib/motion.ts +128 -18
  30. package/src/lib/popup.ts +45 -0
  31. package/src/menu.tsx +7 -4
  32. package/src/menubar.tsx +5 -0
  33. package/src/meter.tsx +1 -1
  34. package/src/minimap.tsx +267 -0
  35. package/src/navigation-menu.tsx +336 -67
  36. package/src/number-field.tsx +327 -61
  37. package/src/pagination.tsx +258 -42
  38. package/src/popover.tsx +3 -1
  39. package/src/preview-card.tsx +3 -1
  40. package/src/progress.tsx +6 -1
  41. package/src/questionnaire.tsx +279 -0
  42. package/src/scroll-area.tsx +1 -1
  43. package/src/select.tsx +8 -6
  44. package/src/sidebar.tsx +109 -28
  45. package/src/slider.tsx +64 -6
  46. package/src/sounds.tsx +6 -10
  47. package/src/spinner.tsx +105 -32
  48. package/src/switch.tsx +2 -2
  49. package/src/table.tsx +82 -15
  50. package/src/tabs.tsx +184 -38
  51. package/src/text-animate.tsx +71 -28
  52. package/src/textarea.tsx +1 -1
  53. package/src/timeline.tsx +321 -0
  54. package/src/toast.tsx +156 -23
  55. package/src/tooltip.tsx +43 -3
  56. package/src/video-player.tsx +296 -120
package/src/index.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  export { Button, buttonVariants, type ButtonProps } from "./button";
2
2
  export { Input, inputVariants, type InputProps } from "./input";
3
+ export {
4
+ Kbd,
5
+ KbdGroup,
6
+ kbdVariants,
7
+ type KbdGroupProps,
8
+ type KbdProps,
9
+ } from "./kbd";
3
10
  export { Badge, badgeVariants, type BadgeProps } from "./badge";
4
11
  export { Switch, type SwitchProps } from "./switch";
5
12
  export { Checkbox, type CheckboxProps } from "./checkbox";
@@ -92,17 +99,27 @@ export {
92
99
  type AvatarFallbackProps,
93
100
  } from "./avatar";
94
101
  export { Calendar, type CalendarProps } from "./calendar";
102
+ export {
103
+ DatePicker,
104
+ type DatePickerProps,
105
+ type DatePickerSingleProps,
106
+ type DatePickerRangeProps,
107
+ } from "./date-picker";
108
+ export type { DateRange } from "react-day-picker";
95
109
  export {
96
110
  Card,
97
111
  CardHeader,
98
112
  CardTitle,
99
113
  CardDescription,
114
+ CardAction,
100
115
  CardContent,
101
116
  CardFooter,
117
+ cardVariants,
102
118
  type CardProps,
103
119
  type CardHeaderProps,
104
120
  type CardTitleProps,
105
121
  type CardDescriptionProps,
122
+ type CardActionProps,
106
123
  type CardContentProps,
107
124
  type CardFooterProps,
108
125
  } from "./card";
@@ -155,6 +172,7 @@ export {
155
172
  CommandItem,
156
173
  CommandEmpty,
157
174
  CommandTitle,
175
+ CommandMeta,
158
176
  CommandDialogTitle,
159
177
  type CommandProps,
160
178
  type CommandTriggerProps,
@@ -174,6 +192,7 @@ export {
174
192
  type CommandItemProps,
175
193
  type CommandEmptyProps,
176
194
  type CommandTitleProps,
195
+ type CommandMetaProps,
177
196
  type CommandDialogTitleProps,
178
197
  } from "./command";
179
198
  export {
@@ -347,6 +366,18 @@ export {
347
366
  type FieldsetProps,
348
367
  type FieldsetLegendProps,
349
368
  } from "./fieldset";
369
+ export {
370
+ FilterGroup,
371
+ FilterItem,
372
+ FilterCount,
373
+ filterGroupVariants,
374
+ filterItemVariants,
375
+ type FilterGroupProps,
376
+ type FilterItemProps,
377
+ type FilterCountProps,
378
+ type FilterGroupVariant,
379
+ type FilterGroupSize,
380
+ } from "./filter-group";
350
381
  export { Form, type FormProps } from "./form";
351
382
  export {
352
383
  Meter,
@@ -403,19 +434,38 @@ export {
403
434
  type MenuViewportProps,
404
435
  } from "./menu";
405
436
  export { Menubar, type MenubarProps } from "./menubar";
437
+ export {
438
+ Minimap,
439
+ minimapVariants,
440
+ type MinimapProps,
441
+ type MinimapSection,
442
+ } from "./minimap";
406
443
  export {
407
444
  NumberField,
408
445
  NumberFieldGroup,
409
446
  NumberFieldInput,
410
447
  NumberFieldIncrement,
411
448
  NumberFieldDecrement,
449
+ NumberFieldStepper,
450
+ NumberFieldPrefix,
451
+ NumberFieldSuffix,
412
452
  NumberFieldScrubArea,
413
453
  NumberFieldScrubAreaCursor,
454
+ numberFieldAffixVariants,
455
+ numberFieldControlVariants,
456
+ numberFieldGroupVariants,
457
+ numberFieldInputVariants,
458
+ numberFieldStepperVariants,
459
+ type NumberFieldSize,
460
+ type NumberFieldAlign,
414
461
  type NumberFieldProps,
415
462
  type NumberFieldGroupProps,
416
463
  type NumberFieldInputProps,
417
464
  type NumberFieldIncrementProps,
418
465
  type NumberFieldDecrementProps,
466
+ type NumberFieldStepperProps,
467
+ type NumberFieldPrefixProps,
468
+ type NumberFieldSuffixProps,
419
469
  type NumberFieldScrubAreaProps,
420
470
  type NumberFieldScrubAreaCursorProps,
421
471
  } from "./number-field";
@@ -432,8 +482,18 @@ export {
432
482
  NavigationMenuPopup,
433
483
  NavigationMenuArrow,
434
484
  NavigationMenuLink,
485
+ NavigationMenuBarLink,
486
+ NavigationMenuLinkIcon,
487
+ NavigationMenuLinkTitle,
488
+ NavigationMenuLinkDescription,
435
489
  NavigationMenuIcon,
490
+ navigationMenuBarItemVariants,
491
+ navigationMenuLinkVariants,
492
+ navigationMenuListVariants,
493
+ navigationMenuRootVariants,
436
494
  navigationMenuTriggerClassName,
495
+ type NavigationMenuVariant,
496
+ type NavigationMenuSize,
437
497
  type NavigationMenuProps,
438
498
  type NavigationMenuListProps,
439
499
  type NavigationMenuItemProps,
@@ -446,6 +506,10 @@ export {
446
506
  type NavigationMenuPopupProps,
447
507
  type NavigationMenuArrowProps,
448
508
  type NavigationMenuLinkProps,
509
+ type NavigationMenuBarLinkProps,
510
+ type NavigationMenuLinkIconProps,
511
+ type NavigationMenuLinkTitleProps,
512
+ type NavigationMenuLinkDescriptionProps,
449
513
  type NavigationMenuIconProps,
450
514
  } from "./navigation-menu";
451
515
  export {
@@ -507,6 +571,47 @@ export {
507
571
  type ProgressTrackProps,
508
572
  type ProgressIndicatorProps,
509
573
  } from "./progress";
574
+ export {
575
+ Questionnaire,
576
+ QuestionnaireProgress,
577
+ QuestionnaireItem,
578
+ QuestionnaireTitle,
579
+ QuestionnaireDescription,
580
+ QuestionnaireChoices,
581
+ QuestionnaireChoice,
582
+ QuestionnaireChoiceInput,
583
+ QuestionnaireChoiceLabel,
584
+ QuestionnaireChoiceShortcut,
585
+ QuestionnaireInput,
586
+ QuestionnaireError,
587
+ QuestionnaireActions,
588
+ QuestionnairePrevious,
589
+ QuestionnaireSkip,
590
+ QuestionnaireNext,
591
+ QuestionnaireSubmit,
592
+ type QuestionnaireProps,
593
+ type QuestionnaireProgressProps,
594
+ type QuestionnaireItemProps,
595
+ type QuestionnaireTitleProps,
596
+ type QuestionnaireDescriptionProps,
597
+ type QuestionnaireChoicesProps,
598
+ type QuestionnaireChoiceProps,
599
+ type QuestionnaireChoiceInputProps,
600
+ type QuestionnaireChoiceLabelProps,
601
+ type QuestionnaireChoiceShortcutProps,
602
+ type QuestionnaireInputProps,
603
+ type QuestionnaireErrorProps,
604
+ type QuestionnaireActionsProps,
605
+ type QuestionnairePreviousProps,
606
+ type QuestionnaireSkipProps,
607
+ type QuestionnaireNextProps,
608
+ type QuestionnaireSubmitProps,
609
+ type QuestionnaireChoiceDefinition,
610
+ type QuestionnaireInputType,
611
+ type QuestionnaireItemDefinition,
612
+ type QuestionnaireItemStatus,
613
+ type QuestionnaireShortcutMode,
614
+ } from "./questionnaire";
510
615
  export {
511
616
  RadioGroup,
512
617
  Radio,
@@ -564,11 +669,14 @@ export {
564
669
  SliderTrack,
565
670
  SliderIndicator,
566
671
  SliderThumb,
672
+ SliderTicks,
673
+ sliderVariants,
567
674
  type SliderProps,
568
675
  type SliderControlProps,
569
676
  type SliderTrackProps,
570
677
  type SliderIndicatorProps,
571
678
  type SliderThumbProps,
679
+ type SliderTicksProps,
572
680
  } from "./slider";
573
681
  export {
574
682
  Tabs,
@@ -576,7 +684,12 @@ export {
576
684
  TabsTab,
577
685
  TabsIndicator,
578
686
  TabsPanel,
687
+ tabsListVariants,
688
+ tabsTabVariants,
689
+ tabsIndicatorVariants,
579
690
  type TabsProps,
691
+ type TabsVariant,
692
+ type TabsSize,
580
693
  type TabsListProps,
581
694
  type TabsTabProps,
582
695
  type TabsIndicatorProps,
@@ -589,6 +702,8 @@ export {
589
702
  ToastViewport,
590
703
  ToastRoot,
591
704
  ToastContent,
705
+ ToastIcon,
706
+ toastRootVariants,
592
707
  ToastTitle,
593
708
  ToastDescription,
594
709
  ToastAction,
@@ -602,6 +717,8 @@ export {
602
717
  type ToastViewportProps,
603
718
  type ToastRootProps,
604
719
  type ToastContentProps,
720
+ type ToastIconProps,
721
+ type ToastType,
605
722
  type ToastTitleProps,
606
723
  type ToastDescriptionProps,
607
724
  type ToastActionProps,
@@ -650,6 +767,7 @@ export {
650
767
  BreadcrumbLink,
651
768
  BreadcrumbPage,
652
769
  BreadcrumbSeparator,
770
+ breadcrumbSeparatorVariants,
653
771
  type BreadcrumbProps,
654
772
  type BreadcrumbListProps,
655
773
  type BreadcrumbItemProps,
@@ -677,14 +795,27 @@ export {
677
795
  PaginationLink,
678
796
  PaginationPrevious,
679
797
  PaginationNext,
798
+ PaginationFirst,
799
+ PaginationLast,
680
800
  PaginationEllipsis,
801
+ PaginationStatus,
802
+ PaginationRange,
803
+ paginationLinkVariants,
804
+ paginationDirectionVariants,
681
805
  type PaginationProps,
682
806
  type PaginationContentProps,
683
807
  type PaginationItemProps,
684
808
  type PaginationLinkProps,
809
+ type PaginationButtonProps,
810
+ type PaginationAnchorProps,
685
811
  type PaginationPreviousProps,
686
812
  type PaginationNextProps,
813
+ type PaginationFirstProps,
814
+ type PaginationLastProps,
687
815
  type PaginationEllipsisProps,
816
+ type PaginationStatusProps,
817
+ type PaginationRangeProps,
818
+ type PaginationSize,
688
819
  } from "./pagination";
689
820
  export {
690
821
  Sidebar,
@@ -693,14 +824,20 @@ export {
693
824
  SidebarFooter,
694
825
  SidebarNav,
695
826
  SidebarNavItem,
827
+ SidebarNavItemLabel,
828
+ SidebarNavBadge,
696
829
  SidebarGroup,
697
830
  SidebarGroupLabel,
831
+ sidebarVariants,
832
+ sidebarNavItemVariants,
698
833
  type SidebarProps,
699
834
  type SidebarHeaderProps,
700
835
  type SidebarContentProps,
701
836
  type SidebarFooterProps,
702
837
  type SidebarNavProps,
703
838
  type SidebarNavItemProps,
839
+ type SidebarNavItemLabelProps,
840
+ type SidebarNavBadgeProps,
704
841
  type SidebarGroupProps,
705
842
  type SidebarGroupLabelProps,
706
843
  } from "./sidebar";
@@ -738,9 +875,15 @@ export {
738
875
  ImageModal,
739
876
  ImageModalTrigger,
740
877
  ImageModalContent,
878
+ ImageModalGallery,
879
+ ImageModalGalleryTrigger,
880
+ imageModalContentVariants,
741
881
  type ImageModalProps,
742
882
  type ImageModalTriggerProps,
743
883
  type ImageModalContentProps,
884
+ type ImageModalGalleryProps,
885
+ type ImageModalGalleryTriggerProps,
886
+ type ImageModalItem,
744
887
  } from "./image-modal";
745
888
  export {
746
889
  Ticker,
@@ -748,6 +891,31 @@ export {
748
891
  type TickerProps,
749
892
  type TickerItemProps,
750
893
  } from "./ticker";
894
+ export {
895
+ Timeline,
896
+ TimelineTrack,
897
+ TimelineItem,
898
+ TimelineMarker,
899
+ TimelineTime,
900
+ TimelineContent,
901
+ TimelineTitle,
902
+ TimelineDescription,
903
+ TimelineMedia,
904
+ timelineVariants,
905
+ timelineTrackVariants,
906
+ timelineItemVariants,
907
+ timelineMarkerVariants,
908
+ timelineTimeVariants,
909
+ type TimelineProps,
910
+ type TimelineTrackProps,
911
+ type TimelineItemProps,
912
+ type TimelineMarkerProps,
913
+ type TimelineTimeProps,
914
+ type TimelineContentProps,
915
+ type TimelineTitleProps,
916
+ type TimelineDescriptionProps,
917
+ type TimelineMediaProps,
918
+ } from "./timeline";
751
919
  export {
752
920
  Table,
753
921
  TableHeader,
@@ -757,6 +925,8 @@ export {
757
925
  TableHead,
758
926
  TableCell,
759
927
  TableCaption,
928
+ tableHeaderVariants,
929
+ tableVariants,
760
930
  type TableProps,
761
931
  type TableHeaderProps,
762
932
  type TableBodyProps,
@@ -773,6 +943,7 @@ export {
773
943
  TooltipPortal,
774
944
  TooltipPositioner,
775
945
  TooltipPopup,
946
+ tooltipPopupVariants,
776
947
  type TooltipProviderProps,
777
948
  type TooltipProps,
778
949
  type TooltipTriggerProps,
@@ -784,23 +955,36 @@ export {
784
955
  CENTRAL_ICON_SIZE,
785
956
  withCentralIconDefaults,
786
957
  iconGallery,
958
+ IconArrowDown,
787
959
  IconBell,
960
+ IconCalendar1,
788
961
  IconChainLink1,
789
962
  IconCheckmark1,
790
963
  IconChevronBottom,
791
964
  IconChevronDownSmall,
792
965
  IconChevronRightSmall,
966
+ IconCircleCheck,
967
+ IconCircleInfo,
793
968
  IconClipboard,
794
969
  IconCrossSmall,
970
+ IconDotGrid1x3Horizontal,
971
+ IconExclamationCircle,
972
+ IconExclamationTriangle,
973
+ IconFullScreen,
795
974
  IconHome,
796
975
  IconMagnifyingGlass,
797
976
  IconMinus,
798
977
  IconMoon,
978
+ IconPause,
799
979
  IconPeople,
980
+ IconPlay,
800
981
  IconPlus,
801
982
  IconSettingsGear1,
802
983
  IconSquareBehindSquare6,
984
+ IconStar,
803
985
  IconSun,
986
+ IconVolumeFull,
987
+ IconVolumeOff,
804
988
  IconX,
805
989
  type CentralIconProps,
806
990
  type CentralIconComponent,
@@ -821,7 +1005,11 @@ export {
821
1005
  IllustrationError,
822
1006
  IllustrationSuccess,
823
1007
  IllustrationSearch,
1008
+ IllustrationUpload,
1009
+ IllustrationFile,
824
1010
  type IllustrationProps,
1011
+ type IllustrationFileProps,
1012
+ type FileIllustrationType,
825
1013
  } from "./illustrations"
826
1014
  export { BrandMark, BrandWordmark, type BrandMarkProps, type BrandWordmarkProps } from "./brand"
827
1015
  export {
package/src/input.tsx CHANGED
@@ -5,7 +5,7 @@ import { forwardRef, type InputHTMLAttributes } from "react"
5
5
  import { cn } from "./lib/cn"
6
6
 
7
7
  const inputVariants = cva(
8
- "text-sm flex w-full cursor-text rounded-md text-fg-primary transition-[color,box-shadow] duration-150 ease-out placeholder:text-fg-quaternary outline-none aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50",
8
+ "text-sm flex w-full cursor-text rounded-md text-fg-primary transition-[color,box-shadow] duration-[var(--duration-sm)] ease-enter placeholder:text-fg-quaternary outline-none aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50",
9
9
  {
10
10
  variants: {
11
11
  variant: {
package/src/kbd.tsx ADDED
@@ -0,0 +1,49 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import type { ComponentProps } from "react"
3
+ import { cn } from "./lib/cn"
4
+
5
+ const kbdVariants = cva(
6
+ // `text-xs` carries the size, line-height and tracking; the weight is set
7
+ // separately because 600 sits above the 400/500 the type scale defines, so
8
+ // there is no `-strong` utility to reach for.
9
+ // The border is bottom-only on purpose: it reads as the lip of a physical
10
+ // key without boxing the glyph in on all four sides, which at 12px turns a
11
+ // keycap into a cramped badge.
12
+ "inline-flex shrink-0 items-center justify-center border-b px-1 font-sans text-xs font-semibold whitespace-nowrap select-none",
13
+ {
14
+ variants: {
15
+ variant: {
16
+ default: "border-border-primary bg-background-tertiary text-fg-secondary",
17
+ // The default fill is a light grey that vanishes on a dark surface, so
18
+ // an inverted keycap tints with the inverted foreground instead. It has
19
+ // to be that token rather than a literal `white`: the grey ramp flips
20
+ // under `.dark`, so a hardcoded white wash would sit invisibly on the
21
+ // now-light inverted surface.
22
+ inverted: "border-fg-inverted/10 bg-fg-inverted/10 text-fg-inverted",
23
+ },
24
+ size: {
25
+ sm: "h-4 min-w-3.5 rounded-2xs",
26
+ md: "h-5 min-w-4 rounded-xs",
27
+ },
28
+ },
29
+ defaultVariants: { variant: "default", size: "md" },
30
+ },
31
+ )
32
+
33
+ export type KbdProps = ComponentProps<"kbd"> & VariantProps<typeof kbdVariants>
34
+ export type KbdGroupProps = ComponentProps<"span">
35
+
36
+ /** A single keycap. Presentational — map "cmd" to ⌘ before it gets here. */
37
+ export const Kbd = ({ className, variant, size, ...props }: KbdProps) => (
38
+ <kbd className={cn(kbdVariants({ variant, size }), className)} {...props} />
39
+ )
40
+
41
+ /** A full shortcut. Keeps the caps on one line at a consistent gap. */
42
+ export const KbdGroup = ({ className, ...props }: KbdGroupProps) => (
43
+ <span
44
+ className={cn("inline-flex items-center gap-1 align-middle", className)}
45
+ {...props}
46
+ />
47
+ )
48
+
49
+ export { kbdVariants }
package/src/lib/motion.ts CHANGED
@@ -1,23 +1,133 @@
1
1
  /**
2
2
  * Shared motion classes for StandardUI.
3
- * Prefer CSS `scale` on centered modals so `translate` centering stays intact.
4
- * Anchored popups use `transform-origin` from Base UI.
3
+ *
4
+ * Every value resolves to a token in `@boyernick/standard-ui-tokens` — the
5
+ * `--ease-*` ladder and the `--duration-*` scale. Nothing here hardcodes a
6
+ * curve or a millisecond count.
7
+ *
8
+ * The easing ladder is ordered by *onset* — how quickly a transition commits:
9
+ *
10
+ * passive ambient, system-initiated (slowest onset)
11
+ * enter surfaces arriving
12
+ * move things travelling across a surface
13
+ * snap direct response to a pointer (sharpest onset)
14
+ *
15
+ * Two kinds of token live here, and they behave differently:
16
+ *
17
+ * Mount tokens — backdrop, popupCenter, popupAnchor, accordionPanel,
18
+ * checkIndicator.
19
+ * These carry separate enter and exit behaviour. Base UI flags the exit
20
+ * with `data-ending-style`, and the `data-ending-style:` variants below
21
+ * re-declare duration and easing for that direction. Exits always run at
22
+ * `--duration-exit`, so dismissing anything costs the same no matter how
23
+ * long the entrance took.
24
+ *
25
+ * State tokens — tabsIndicator, colors, transform, all.
26
+ * These animate between states on a mounted element, so there is no enter
27
+ * or exit to distinguish; they take a single curve.
28
+ *
29
+ * Fades are linear on purpose. Perceived brightness is already non-linear, so
30
+ * easing an opacity change makes it look like it sticks at the end.
5
31
  */
32
+
33
+ const DURATION = {
34
+ xs: "duration-[var(--duration-xs)]",
35
+ sm: "duration-[var(--duration-sm)]",
36
+ md: "duration-[var(--duration-md)]",
37
+ lg: "duration-[var(--duration-lg)]",
38
+ } as const
39
+
40
+ /** Every exit runs at the same length — see `--duration-exit`. */
41
+ const EXIT_DURATION = "data-ending-style:duration-[var(--duration-exit)]"
42
+
43
+ const REDUCED = "motion-reduce:transition-none"
44
+
6
45
  export const motion = {
7
- backdrop:
8
- "transition-opacity duration-150 ease-out motion-reduce:transition-none data-starting-style:opacity-0 data-ending-style:opacity-0",
9
- /** Centered dialog / alert — scale property, not transform */
10
- popupCenter:
11
- "transition-[scale,opacity] duration-150 ease-out motion-reduce:transition-none data-starting-style:scale-[0.96] data-starting-style:opacity-0 data-ending-style:scale-[0.96] data-ending-style:opacity-0",
12
- /** Select, tooltip, menus — transform-origin from Base UI */
13
- popupAnchor:
14
- "origin-[var(--transform-origin)] transition-[transform,scale,opacity] duration-150 ease-out motion-reduce:transition-none data-starting-style:scale-[0.96] data-starting-style:opacity-0 data-ending-style:scale-[0.96] data-ending-style:opacity-0",
15
- accordionPanel:
16
- "transition-[height] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none data-starting-style:h-0 data-ending-style:h-0",
17
- tabsIndicator:
18
- "transition-[translate,width] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
19
- colors: "transition-colors duration-150 ease-out motion-reduce:transition-none",
20
- transform:
21
- "transition-transform duration-150 ease-out motion-reduce:transition-none",
22
- all: "transition-all duration-150 ease-out motion-reduce:transition-none",
46
+ /** Overlay scrims. Linear both ways — see the note on fades above. */
47
+ backdrop: [
48
+ "transition-opacity",
49
+ DURATION.sm,
50
+ "ease-linear",
51
+ REDUCED,
52
+ "data-starting-style:opacity-0",
53
+ "data-ending-style:opacity-0",
54
+ EXIT_DURATION,
55
+ "data-ending-style:ease-linear",
56
+ ].join(" "),
57
+
58
+ /** Centred dialog / alert — animates `scale`, so translate centring holds. */
59
+ popupCenter: [
60
+ "transition-[scale,opacity]",
61
+ DURATION.md,
62
+ "ease-enter",
63
+ REDUCED,
64
+ "data-starting-style:scale-[0.96] data-starting-style:opacity-0",
65
+ "data-ending-style:scale-[0.96] data-ending-style:opacity-0",
66
+ EXIT_DURATION,
67
+ "data-ending-style:ease-move",
68
+ ].join(" "),
69
+
70
+ /** Select, tooltip, menus — transform-origin comes from Base UI. */
71
+ popupAnchor: [
72
+ "origin-[var(--transform-origin)]",
73
+ "transition-[transform,scale,opacity]",
74
+ DURATION.sm,
75
+ "ease-enter",
76
+ REDUCED,
77
+ "data-starting-style:scale-[0.96] data-starting-style:opacity-0",
78
+ "data-ending-style:scale-[0.96] data-ending-style:opacity-0",
79
+ EXIT_DURATION,
80
+ "data-ending-style:ease-move",
81
+ ].join(" "),
82
+
83
+ /** Height expand and collapse. */
84
+ accordionPanel: [
85
+ "transition-[height]",
86
+ DURATION.md,
87
+ "ease-move",
88
+ REDUCED,
89
+ "data-starting-style:h-0",
90
+ "data-ending-style:h-0",
91
+ EXIT_DURATION,
92
+ "data-ending-style:ease-move",
93
+ ].join(" "),
94
+
95
+ /** Check and minus glyphs mounting inside a small control. Snaps, because
96
+ * it answers a click directly, and scales from the centre so the mark
97
+ * reads as landing in the box rather than fading onto it. */
98
+ checkIndicator: [
99
+ "transition-[scale,opacity]",
100
+ DURATION.sm,
101
+ "ease-snap",
102
+ REDUCED,
103
+ "data-starting-style:scale-50 data-starting-style:opacity-0",
104
+ "data-ending-style:scale-50 data-ending-style:opacity-0",
105
+ EXIT_DURATION,
106
+ "data-ending-style:ease-move",
107
+ ].join(" "),
108
+
109
+ /** Sliding tab underline or pill — follows a pointer, so it snaps. */
110
+ tabsIndicator: [
111
+ "transition-[translate,width]",
112
+ DURATION.md,
113
+ "ease-snap",
114
+ REDUCED,
115
+ ].join(" "),
116
+
117
+ /** Hover and pressed colour changes. */
118
+ colors: ["transition-colors", DURATION.sm, "ease-enter", REDUCED].join(" "),
119
+
120
+ /** Icon rotates and simple transforms. */
121
+ transform: [
122
+ "transition-transform",
123
+ DURATION.sm,
124
+ "ease-move",
125
+ REDUCED,
126
+ ].join(" "),
127
+
128
+ /** Every property. Reach for a narrower token first. */
129
+ all: ["transition-all", DURATION.sm, "ease-enter", REDUCED].join(" "),
130
+
131
+ /** Long, ambient movement — progress fills, meters, background shifts. */
132
+ passive: ["transition-all", DURATION.lg, "ease-passive", REDUCED].join(" "),
23
133
  } as const
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared surface classes for anchored popups — autocomplete, combobox, select,
3
+ * menu, context menu, and anything else that floats a panel next to a trigger.
4
+ *
5
+ * These live in one place because they have to agree. A popup that picks its
6
+ * own radius or hairline reads as a different component to the one beside it,
7
+ * and the drift is invisible until two of them sit on the same page.
8
+ *
9
+ * The radii are related, not independent. `item` is nested inside `inset`, so
10
+ * it follows the nested-radius rule:
11
+ *
12
+ * inner = outer − padding 6px = 12px − 6px
13
+ *
14
+ * Change `surface`'s radius or `inset`'s padding and `item` has to move with
15
+ * it, or the corners stop being concentric. See `.cursor/rules/nested-radius`.
16
+ */
17
+
18
+ /** Radius, hairline and elevation. Add `overflow-hidden` where items can reach
19
+ * the corners; leave it off panels that nest their own popups.
20
+ *
21
+ * No `border` here on purpose. Every shadow token composes
22
+ * `--elevation-hairline`, so `shadow-lg` already draws the edge — and that
23
+ * hairline flips colour with the theme (black on light, white on dark) and
24
+ * thins to 0.5px on hi-dpi displays. A border on top stacks a second edge
25
+ * just outside the first, which reads as one muddy line darker than either,
26
+ * and none of that tracking comes with it. Leaving it off also keeps the
27
+ * edge out of the box model, so `popupInset` is the whole inset. */
28
+ export const popupSurface = "rounded-xl bg-surface shadow-lg outline-none"
29
+
30
+ /** Padding around a list of items. Collapses when there is nothing to pad, so
31
+ * a sibling empty state is not pushed off-centre by a list of zero items.
32
+ *
33
+ * 6px is the whole inset — `popupSurface` carries no border to add to it —
34
+ * which is what keeps 12 − 6 = 6 landing exactly on `popupItem`'s radius. */
35
+ export const popupInset = "p-1.5 empty:p-0"
36
+
37
+ /** One selectable row. */
38
+ export const popupItem =
39
+ "flex min-h-9 cursor-default items-center gap-2 rounded-sm px-3 py-2 text-sm text-fg-primary outline-none select-none"
40
+
41
+ /** A group heading. Shares the item's left edge. */
42
+ export const popupLabel = "px-3 py-1.5 text-xs text-fg-tertiary"
43
+
44
+ /** Empty and status text. Shares the item's left edge. */
45
+ export const popupMessage = "px-3 py-2 text-sm text-fg-tertiary"
package/src/menu.tsx CHANGED
@@ -5,6 +5,7 @@ import type { ComponentProps } from "react"
5
5
  import { IconCheckmark1, IconChevronRightSmall } from "./icons"
6
6
  import { cn } from "./lib/cn"
7
7
  import { motion } from "./lib/motion"
8
+ import { popupInset, popupItem, popupLabel, popupSurface } from "./lib/popup"
8
9
 
9
10
  export type MenuProps = ComponentProps<typeof BaseMenu.Root>
10
11
  export type MenuTriggerProps = ComponentProps<typeof BaseMenu.Trigger>
@@ -34,7 +35,7 @@ export type MenuSubmenuTriggerProps = ComponentProps<
34
35
  export type MenuViewportProps = ComponentProps<typeof BaseMenu.Viewport>
35
36
 
36
37
  const menuItemClassName = cn(
37
- "flex min-h-8 cursor-default items-center gap-2 rounded-xs px-2.5 py-1.5 text-sm text-fg-primary outline-none select-none",
38
+ popupItem,
38
39
  motion.colors,
39
40
  "data-disabled:cursor-not-allowed data-disabled:opacity-50 data-highlighted:bg-background-tertiary",
40
41
  )
@@ -59,7 +60,7 @@ export const MenuBackdrop = ({ className, ...props }: MenuBackdropProps) => (
59
60
  )
60
61
 
61
62
  export const MenuPositioner = ({
62
- sideOffset = 4,
63
+ sideOffset = 8,
63
64
  className,
64
65
  ...props
65
66
  }: MenuPositionerProps) => (
@@ -73,7 +74,9 @@ export const MenuPositioner = ({
73
74
  export const MenuPopup = ({ className, ...props }: MenuPopupProps) => (
74
75
  <BaseMenu.Popup
75
76
  className={cn(
76
- "z-50 min-w-40 overflow-hidden rounded-md border border-border-primary bg-surface p-1 shadow-md outline-none",
77
+ "z-50 min-w-40 overflow-hidden",
78
+ popupSurface,
79
+ popupInset,
77
80
  motion.popupAnchor,
78
81
  className,
79
82
  )}
@@ -123,7 +126,7 @@ export const MenuGroupLabel = ({
123
126
  ...props
124
127
  }: MenuGroupLabelProps) => (
125
128
  <BaseMenu.GroupLabel
126
- className={cn("px-2.5 py-1.5 text-xs text-fg-tertiary", className)}
129
+ className={cn(popupLabel, className)}
127
130
  {...props}
128
131
  />
129
132
  )