@mieweb/ui 0.10.0-dev.3 → 0.10.0-dev.4

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.
package/dist/index.d.cts CHANGED
@@ -10194,8 +10194,27 @@ interface SidebarNavGroupProps {
10194
10194
  groupId?: string;
10195
10195
  /** Additional CSS classes */
10196
10196
  className?: string;
10197
+ /**
10198
+ * Keep items mounted when the group is collapsed, hidden via `hidden`.
10199
+ *
10200
+ * For groups whose children own DOM state that a remount would destroy —
10201
+ * uncontrolled inputs, media playback position, an editor instance. State
10202
+ * survives the group's own collapse *and* the desktop rail collapsing.
10203
+ *
10204
+ * Does **not** make collapsed items findable by in-page search: `hidden` is
10205
+ * `display: none`, and browsers do not match text inside it. `hidden="until-found"`
10206
+ * would deliver that, but it is not portable enough to build the API on yet.
10207
+ *
10208
+ * Mirrors `CollapsibleContent`'s prop of the same name, and carries the same
10209
+ * caveat: this path does **not** animate. `hidden` is `display: none`, which
10210
+ * an animated height cannot run through, and dropping it for the duration
10211
+ * would let keyboard users tab into content that is visually collapsed. Not
10212
+ * animating is the safe answer until `hidden` can be sequenced around the
10213
+ * animation.
10214
+ */
10215
+ forceMount?: boolean;
10197
10216
  }
10198
- declare function SidebarNavGroup({ label, icon, children, defaultExpanded, groupId, className, }: SidebarNavGroupProps): React__default.JSX.Element;
10217
+ declare function SidebarNavGroup({ label, icon, children, defaultExpanded, groupId, className, forceMount, }: SidebarNavGroupProps): React__default.JSX.Element;
10199
10218
  interface SidebarNavItemProps {
10200
10219
  /** Item label */
10201
10220
  label: string;
package/dist/index.d.ts CHANGED
@@ -10194,8 +10194,27 @@ interface SidebarNavGroupProps {
10194
10194
  groupId?: string;
10195
10195
  /** Additional CSS classes */
10196
10196
  className?: string;
10197
+ /**
10198
+ * Keep items mounted when the group is collapsed, hidden via `hidden`.
10199
+ *
10200
+ * For groups whose children own DOM state that a remount would destroy —
10201
+ * uncontrolled inputs, media playback position, an editor instance. State
10202
+ * survives the group's own collapse *and* the desktop rail collapsing.
10203
+ *
10204
+ * Does **not** make collapsed items findable by in-page search: `hidden` is
10205
+ * `display: none`, and browsers do not match text inside it. `hidden="until-found"`
10206
+ * would deliver that, but it is not portable enough to build the API on yet.
10207
+ *
10208
+ * Mirrors `CollapsibleContent`'s prop of the same name, and carries the same
10209
+ * caveat: this path does **not** animate. `hidden` is `display: none`, which
10210
+ * an animated height cannot run through, and dropping it for the duration
10211
+ * would let keyboard users tab into content that is visually collapsed. Not
10212
+ * animating is the safe answer until `hidden` can be sequenced around the
10213
+ * animation.
10214
+ */
10215
+ forceMount?: boolean;
10197
10216
  }
10198
- declare function SidebarNavGroup({ label, icon, children, defaultExpanded, groupId, className, }: SidebarNavGroupProps): React__default.JSX.Element;
10217
+ declare function SidebarNavGroup({ label, icon, children, defaultExpanded, groupId, className, forceMount, }: SidebarNavGroupProps): React__default.JSX.Element;
10199
10218
  interface SidebarNavItemProps {
10200
10219
  /** Item label */
10201
10220
  label: string;
package/dist/index.js CHANGED
@@ -147,6 +147,7 @@ import { isStorybookDocsMode } from './chunk-VSQF22GL.js';
147
147
  export { isStorybookDocsMode } from './chunk-VSQF22GL.js';
148
148
  import { Plus, GripVertical, RefreshCw, Pencil, Trash2, AlertCircle, ArrowLeft, Users, FilePlus, CheckCircle, ChevronDown, Clock, FileText, ClipboardList, Stethoscope, Mail, Phone, ClipboardCheck, Eye, Info, Link, ChevronUp, History, StickyNote, ExternalLink, MoreVertical, Send, Scan, TestTube2, Pill, Share, User, Download, ChevronRight, Calendar, Printer, ClipboardPlus, FileCheck, Wheat, Bell, FileHeart, HeartPulse, Paperclip, HelpCircle, MoreHorizontal, AlertTriangle, Upload as Upload$1, Camera, Check as Check$1, X as X$1, ScanLine, Image } from './chunk-BKSPDTX4.js';
149
149
  export { Accessibility as AccessibilityIcon, Activity as ActivityIcon, AlertCircle as AlertCircleIcon, AlertTriangle as AlertTriangleIcon, Wheat as AllergyIcon, WheatOff as AllergyOffIcon, Ambulance as AmbulanceIcon, ArrowLeft as ArrowLeftIcon, ArrowRight as ArrowRightIcon, ArrowUpDown as ArrowUpDownIcon, ArrowUp as ArrowUpIcon, AtSign as AtSignIcon, Baby as BabyIcon, BadgeAlert as BadgeAlertIcon, BadgeCheck as BadgeCheckIcon, BadgeMinus as BadgeMinusIcon, BadgePlus as BadgePlusIcon, BadgeX as BadgeXIcon, Ban as BanIcon, Bandage as BandageIcon, BedDouble as BedDoubleIcon, Bed as BedIcon, BedSingle as BedSingleIcon, Bell as BellIcon, BellOff as BellOffIcon, Biohazard as BiohazardIcon, Bone as BoneIcon, BookOpenCheck as BookOpenCheckIcon, BookOpen as BookOpenIcon, Bookmark as BookmarkIcon, Bot as BotIcon, Brain as BrainIcon, Briefcase as BriefcaseIcon, BriefcaseMedical as BriefcaseMedicalIcon, Building as BuildingIcon, CalendarCheck as CalendarCheckIcon, Calendar as CalendarIcon, Camera as CameraIcon, Cannabis as CannabisIcon, BarChart3 as ChartIcon, CheckCircle as CheckCircleIcon, Check as CheckIcon, ChevronDown as ChevronDownIcon, ChevronLeft as ChevronLeftIcon, ChevronRight as ChevronRightIcon, ChevronUp as ChevronUpIcon, Cigarette as CigaretteIcon, CigaretteOff as CigaretteOffIcon, CircleDotDashed as CircleDotDashedIcon, CircleDot as CircleDotIcon, CircleUser as CircleUserIcon, ClipboardCheck as ClipboardCheckIcon, Clipboard as ClipboardIcon, ClipboardList as ClipboardListIcon, ClipboardPlus as ClipboardPlusIcon, Clock as ClockIcon, Cog as CogIcon, Columns as ColumnsIcon, Copy as CopyIcon, CreditCard as CreditCardIcon, Cross as CrossIcon, Dna as DnaIcon, DollarSign as DollarSignIcon, Download as DownloadIcon, Droplet as DropletIcon, Droplets as DropletsIcon, Ear as EarIcon, ExternalLink as ExternalLinkIcon, Eye as EyeIcon, EyeOff as EyeOffIcon, FileCheck2 as FileCheck2Icon, FileCheck as FileCheckIcon, FileHeart as FileHeartIcon, File as FileIcon, FilePlus2 as FilePlus2Icon, FilePlus as FilePlusIcon, FileText as FileTextIcon, Filter as FilterIcon, Fingerprint as FingerprintIcon, Flag as FlagIcon, FlaskConical as FlaskIcon, FlaskRound as FlaskRoundIcon, Folder as FolderIcon, FolderOpen as FolderOpenIcon, Footprints as FootprintsIcon, Glasses as GlassesIcon, Globe as GlobeIcon, LayoutGrid as GridIcon, GripVertical as GripVerticalIcon, Hand as HandIcon, HandMetal as HandMetalIcon, Hash as HashIcon, Heart as HeartIcon, HeartOff as HeartOffIcon, HeartPulse as HeartPulseIcon, HelpCircle as HelpCircleIcon, History as HistoryIcon, Home as HomeIcon, Hospital as HospitalIcon, Image as ImageIcon, Info as InfoIcon, Key as KeyIcon, Link as LinkIcon, List as ListIcon, Loader2 as LoaderIcon, Lock as LockIcon, LogIn as LogInIcon, LogOut as LogOutIcon, Mail as MailIcon, MapPin as MapPinIcon, Maximize as MaximizeIcon, Menu as MenuIcon, MessageSquare as MessageIcon, Mic as MicIcon, Microscope as MicroscopeIcon, Minimize as MinimizeIcon, Minus as MinusIcon, Monitor as MonitorIcon, Moon as MoonIcon, MoreHorizontal as MoreHorizontalIcon, MoreVertical as MoreVerticalIcon, NotebookPen as NotebookPenIcon, Palette as PaletteIcon, Paperclip as PaperclipIcon, CircleUserRound as PatientIcon, Pencil as PencilIcon, UserRound as PersonIcon, Phone as PhoneIcon, PieChart as PieChartIcon, Pill as PillIcon, Plus as PlusIcon, Printer as PrinterIcon, Radiation as RadiationIcon, Ruler as RulerIcon, Save as SaveIcon, Scale as ScaleIcon, Scaling as ScalingIcon, ScanEye as ScanEyeIcon, Scan as ScanIcon, ScanLine as ScanLineIcon, Search as SearchIcon, Settings as SettingsIcon, Share as ShareIcon, ShieldCheck as ShieldCheckIcon, Shield as ShieldIcon, ShieldPlus as ShieldPlusIcon, Sliders as SlidersIcon, Sparkle as SparkleIcon, Star as StarIcon, Stethoscope as StethoscopeIcon, StickyNote as StickyNoteIcon, Square as StopIcon, Sun as SunIcon, Syringe as SyringeIcon, Table as TableIcon, Tablets as TabletsIcon, Tag as TagIcon, TestTube2 as TestTubeIcon, TestTubes as TestTubesIcon, Thermometer as ThermometerIcon, ThermometerSun as ThermometerSunIcon, ThumbsDown as ThumbsDownIcon, ThumbsUp as ThumbsUpIcon, Timer as TimerIcon, Trash2 as TrashIcon, TrendingDown as TrendingDownIcon, TrendingUp as TrendingUpIcon, Truck as TruckIcon, Unlock as UnlockIcon, Upload as UploadIcon, UserCheck as UserCheckIcon, User as UserIcon, UserMinus as UserMinusIcon, UserPlus as UserPlusIcon, UserRoundCheck as UserRoundCheckIcon, UserRoundMinus as UserRoundMinusIcon, UserRoundPlus as UserRoundPlusIcon, UserRoundX as UserRoundXIcon, Users as UsersIcon, Weight as WeightIcon, Wrench as WrenchIcon, XCircle as XCircleIcon, X2 as XIcon, Zap as ZapIcon } from './chunk-BKSPDTX4.js';
150
+ import { usePrefersReducedMotion } from './chunk-HB7C7NB5.js';
150
151
  export { usePrefersReducedMotion } from './chunk-HB7C7NB5.js';
151
152
  import { Badge } from './chunk-BMSG4NBH.js';
152
153
  export { Badge, badgeVariants } from './chunk-BMSG4NBH.js';
@@ -159,7 +160,7 @@ import { cn } from './chunk-TQONQSJO.js';
159
160
  export { cn, miewebUITwMergeConflicts } from './chunk-TQONQSJO.js';
160
161
  import './chunk-PX6F3LHL.js';
161
162
  import * as React13 from 'react';
162
- import React13__default, { createContext, useState, useEffect, useCallback, useMemo, useContext, useRef } from 'react';
163
+ import React13__default, { createContext, useState, useEffect, useCallback, useMemo, useContext, useRef, useId } from 'react';
163
164
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
164
165
  import { cva } from 'class-variance-authority';
165
166
  import * as ReactDOM from 'react-dom';
@@ -40711,86 +40712,149 @@ function SidebarNavGroup({
40711
40712
  children,
40712
40713
  defaultExpanded = false,
40713
40714
  groupId,
40714
- className
40715
+ className,
40716
+ forceMount
40715
40717
  }) {
40716
40718
  const { isCollapsed, isMobileViewport, expandedGroup, toggleGroup } = useSidebar();
40717
40719
  const showCollapsed = !isMobileViewport && isCollapsed;
40720
+ const prefersReducedMotion = usePrefersReducedMotion();
40721
+ const contentId = useId();
40722
+ const groupRef = useRef(null);
40723
+ const triggerRef = useRef(null);
40718
40724
  const isExpanded = groupId ? expandedGroup === groupId : defaultExpanded;
40719
40725
  const [localExpanded, setLocalExpanded] = useState(defaultExpanded);
40720
40726
  const effectiveExpanded = groupId ? isExpanded : localExpanded;
40727
+ const itemsVisible = !showCollapsed && effectiveExpanded;
40728
+ const focusWasInsideRef = useRef(false);
40729
+ const captureFocusInside = useCallback(() => {
40730
+ const active = document.activeElement;
40731
+ focusWasInsideRef.current = active !== triggerRef.current && groupRef.current?.contains(active) === true;
40732
+ }, []);
40733
+ useEffect(() => {
40734
+ if (!itemsVisible) return;
40735
+ function trackFocus(event) {
40736
+ const target = event.target;
40737
+ focusWasInsideRef.current = target !== triggerRef.current && groupRef.current?.contains(target) === true;
40738
+ }
40739
+ document.addEventListener("focusin", trackFocus);
40740
+ return () => document.removeEventListener("focusin", trackFocus);
40741
+ }, [itemsVisible]);
40742
+ useEffect(() => {
40743
+ if (itemsVisible) return;
40744
+ if (focusWasInsideRef.current) {
40745
+ focusWasInsideRef.current = false;
40746
+ triggerRef.current?.focus();
40747
+ }
40748
+ }, [itemsVisible]);
40721
40749
  const handleToggle = useCallback(() => {
40750
+ captureFocusInside();
40722
40751
  if (groupId) {
40723
40752
  toggleGroup(groupId);
40724
40753
  } else {
40725
40754
  setLocalExpanded((prev) => !prev);
40726
40755
  }
40727
- }, [groupId, toggleGroup]);
40728
- return /* @__PURE__ */ jsxs("div", { "data-slot": "sidebar-nav-group", className: cn("mb-2", className), children: [
40729
- /* @__PURE__ */ jsxs(
40730
- "button",
40731
- {
40732
- "data-slot": "sidebar-nav-group-button",
40733
- onClick: handleToggle,
40734
- className: cn(
40735
- "flex w-full items-center rounded-lg px-3 py-2 text-sm font-semibold",
40736
- "text-neutral-700 dark:text-neutral-300",
40737
- "transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-800",
40738
- showCollapsed && "justify-center"
40739
- ),
40740
- title: showCollapsed ? label : void 0,
40741
- "aria-label": showCollapsed ? label : void 0,
40742
- children: [
40743
- icon && /* @__PURE__ */ jsx(
40744
- "span",
40745
- {
40746
- className: cn(
40747
- "text-muted-foreground h-5 w-5 flex-shrink-0",
40748
- !showCollapsed && "me-3"
40749
- ),
40750
- children: icon
40751
- }
40752
- ),
40753
- /* @__PURE__ */ jsxs(AnimatedPresence, { initial: false, children: [
40754
- !showCollapsed && /* @__PURE__ */ jsx(
40755
- Animated,
40756
- {
40757
- as: "span",
40758
- preset: "sidebarLabel",
40759
- mode: "presence",
40760
- className: "flex-1 truncate text-start",
40761
- children: label
40762
- },
40763
- "label"
40756
+ }, [captureFocusInside, groupId, toggleGroup]);
40757
+ const items = /* @__PURE__ */ jsx("div", { className: "mt-1 ps-2", children });
40758
+ const panelInDom = forceMount || itemsVisible;
40759
+ return /* @__PURE__ */ jsxs(
40760
+ "div",
40761
+ {
40762
+ ref: groupRef,
40763
+ "data-slot": "sidebar-nav-group",
40764
+ className: cn("mb-2", className),
40765
+ children: [
40766
+ /* @__PURE__ */ jsxs(
40767
+ "button",
40768
+ {
40769
+ ref: triggerRef,
40770
+ "data-slot": "sidebar-nav-group-button",
40771
+ onClick: handleToggle,
40772
+ "aria-expanded": itemsVisible,
40773
+ "aria-controls": panelInDom ? contentId : void 0,
40774
+ className: cn(
40775
+ "flex w-full items-center rounded-lg px-3 py-2 text-sm font-semibold",
40776
+ "text-neutral-700 dark:text-neutral-300",
40777
+ "transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-800",
40778
+ showCollapsed && "justify-center"
40764
40779
  ),
40765
- !showCollapsed && /* @__PURE__ */ jsx(
40766
- Animated,
40767
- {
40768
- as: "span",
40769
- preset: "sidebarLabel",
40770
- mode: "presence",
40771
- className: cn(
40772
- "ms-2 flex-shrink-0 transition-transform duration-200",
40773
- effectiveExpanded && "rotate-180"
40780
+ title: showCollapsed ? label : void 0,
40781
+ "aria-label": showCollapsed ? label : void 0,
40782
+ children: [
40783
+ icon && /* @__PURE__ */ jsx(
40784
+ "span",
40785
+ {
40786
+ className: cn(
40787
+ "text-muted-foreground h-5 w-5 flex-shrink-0",
40788
+ !showCollapsed && "me-3"
40789
+ ),
40790
+ children: icon
40791
+ }
40792
+ ),
40793
+ /* @__PURE__ */ jsxs(AnimatedPresence, { initial: false, children: [
40794
+ !showCollapsed && /* @__PURE__ */ jsx(
40795
+ Animated,
40796
+ {
40797
+ as: "span",
40798
+ preset: "sidebarLabel",
40799
+ mode: "presence",
40800
+ className: "flex-1 truncate text-start",
40801
+ children: label
40802
+ },
40803
+ "label"
40774
40804
  ),
40775
- children: /* @__PURE__ */ jsx(ChevronDownIcon4, {})
40776
- },
40777
- "chevron"
40778
- )
40779
- ] })
40780
- ]
40781
- }
40782
- ),
40783
- !showCollapsed && /* @__PURE__ */ jsx(
40784
- "div",
40785
- {
40786
- className: cn(
40787
- "overflow-hidden transition-all duration-300",
40788
- effectiveExpanded ? "mt-1 max-h-[1000px] opacity-100" : "max-h-0 opacity-0"
40805
+ !showCollapsed && /* @__PURE__ */ jsx(
40806
+ Animated,
40807
+ {
40808
+ as: "span",
40809
+ preset: "sidebarLabel",
40810
+ mode: "presence",
40811
+ className: cn(
40812
+ "ms-2 flex-shrink-0 transition-transform duration-200",
40813
+ effectiveExpanded && "rotate-180"
40814
+ ),
40815
+ children: /* @__PURE__ */ jsx(ChevronDownIcon4, {})
40816
+ },
40817
+ "chevron"
40818
+ )
40819
+ ] })
40820
+ ]
40821
+ }
40789
40822
  ),
40790
- children: /* @__PURE__ */ jsx("div", { className: "ps-2", children })
40791
- }
40792
- )
40793
- ] });
40823
+ forceMount ? (
40824
+ /*
40825
+ * Deliberately outside the rail-collapsed gate. Gating it there would
40826
+ * unmount the items whenever the sidebar collapsed, destroying exactly
40827
+ * the state this prop exists to preserve — the prop would hold its
40828
+ * promise for the group's own toggle and quietly break it for the
40829
+ * rail's, which is worse than not offering it.
40830
+ */
40831
+ /* @__PURE__ */ jsx(
40832
+ "div",
40833
+ {
40834
+ id: contentId,
40835
+ "data-slot": "sidebar-nav-group-items",
40836
+ "data-state": itemsVisible ? "open" : "closed",
40837
+ hidden: !itemsVisible,
40838
+ children: items
40839
+ }
40840
+ )
40841
+ ) : !showCollapsed && /* @__PURE__ */ jsx(AnimatedPresence, { initial: false, children: effectiveExpanded && /* @__PURE__ */ jsx(
40842
+ Animated,
40843
+ {
40844
+ id: contentId,
40845
+ "data-slot": "sidebar-nav-group-items",
40846
+ "data-state": "open",
40847
+ preset: "collapse",
40848
+ mode: "presence",
40849
+ enabled: !prefersReducedMotion,
40850
+ className: "overflow-hidden",
40851
+ children: items
40852
+ },
40853
+ "items"
40854
+ ) })
40855
+ ]
40856
+ }
40857
+ );
40794
40858
  }
40795
40859
  function SidebarNavItem({
40796
40860
  label,