tempest-react-sdk 0.73.0 → 0.75.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.
- package/README.md +6 -4
- package/dist/br/BrazilMap.module.cjs.map +1 -1
- package/dist/br/BrazilMap.module.js.map +1 -1
- package/dist/br/MapMarkers.cjs +1 -1
- package/dist/br/MapMarkers.cjs.map +1 -1
- package/dist/br/MapMarkers.js +19 -22
- package/dist/br/MapMarkers.js.map +1 -1
- package/dist/br/MunicipalitySearch.cjs +1 -1
- package/dist/br/MunicipalitySearch.cjs.map +1 -1
- package/dist/br/MunicipalitySearch.js +66 -48
- package/dist/br/MunicipalitySearch.js.map +1 -1
- package/dist/br/MunicipalitySearch.module.cjs +1 -1
- package/dist/br/MunicipalitySearch.module.cjs.map +1 -1
- package/dist/br/MunicipalitySearch.module.js +4 -3
- package/dist/br/MunicipalitySearch.module.js.map +1 -1
- package/dist/br.d.ts +12 -0
- package/dist/components/AIChat/AIChat.module.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.module.js.map +1 -1
- package/dist/components/AIChat/AIChatTurn.cjs +1 -1
- package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
- package/dist/components/AIChat/AIChatTurn.js +1 -1
- package/dist/components/AIChat/AIChatTurn.js.map +1 -1
- package/dist/components/AppBar/AppBar.cjs +1 -1
- package/dist/components/AppBar/AppBar.cjs.map +1 -1
- package/dist/components/AppBar/AppBar.js +1 -0
- package/dist/components/AppBar/AppBar.js.map +1 -1
- package/dist/components/AppBar/AppBar.module.cjs +1 -1
- package/dist/components/AppBar/AppBar.module.cjs.map +1 -1
- package/dist/components/AppBar/AppBar.module.js +12 -11
- package/dist/components/AppBar/AppBar.module.js.map +1 -1
- package/dist/components/AppShell/AppShell.module.cjs.map +1 -1
- package/dist/components/AppShell/AppShell.module.js.map +1 -1
- package/dist/components/Button/Button.cjs +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js +41 -36
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Button/Button.module.cjs.map +1 -1
- package/dist/components/Button/Button.module.js.map +1 -1
- package/dist/components/Button/ButtonContent.cjs +2 -0
- package/dist/components/Button/ButtonContent.cjs.map +1 -0
- package/dist/components/Button/ButtonContent.js +34 -0
- package/dist/components/Button/ButtonContent.js.map +1 -0
- package/dist/components/Button/ButtonSlot.cjs +2 -0
- package/dist/components/Button/ButtonSlot.cjs.map +1 -0
- package/dist/components/Button/ButtonSlot.js +36 -0
- package/dist/components/Button/ButtonSlot.js.map +1 -0
- package/dist/components/Button/button-class.cjs +2 -0
- package/dist/components/Button/button-class.cjs.map +1 -0
- package/dist/components/Button/button-class.js +13 -0
- package/dist/components/Button/button-class.js.map +1 -0
- package/dist/components/Button/link-rel.cjs +2 -0
- package/dist/components/Button/link-rel.cjs.map +1 -0
- package/dist/components/Button/link-rel.js +11 -0
- package/dist/components/Button/link-rel.js.map +1 -0
- package/dist/components/Button/slot.cjs +2 -0
- package/dist/components/Button/slot.cjs.map +1 -0
- package/dist/components/Button/slot.js +41 -0
- package/dist/components/Button/slot.js.map +1 -0
- package/dist/components/Carousel/Carousel.module.cjs.map +1 -1
- package/dist/components/Carousel/Carousel.module.js.map +1 -1
- package/dist/components/Chat/Chat.module.cjs.map +1 -1
- package/dist/components/Chat/Chat.module.js.map +1 -1
- package/dist/components/Collapsible/Collapsible.cjs +1 -1
- package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
- package/dist/components/Collapsible/Collapsible.js +1 -1
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js +18 -23
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Combobox/use-listbox-dismiss.cjs +2 -0
- package/dist/components/Combobox/use-listbox-dismiss.cjs.map +1 -0
- package/dist/components/Combobox/use-listbox-dismiss.js +34 -0
- package/dist/components/Combobox/use-listbox-dismiss.js.map +1 -0
- package/dist/components/Dropzone/Dropzone.module.cjs.map +1 -1
- package/dist/components/Dropzone/Dropzone.module.js.map +1 -1
- package/dist/components/FilterBar/FilterBar.module.cjs.map +1 -1
- package/dist/components/FilterBar/FilterBar.module.js.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.module.cjs.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.module.js.map +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.module.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.module.js.map +1 -1
- package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
- package/dist/components/InstallButton/InstallButton.js.map +1 -1
- package/dist/components/ListTile/ListTile.module.cjs.map +1 -1
- package/dist/components/ListTile/ListTile.module.js.map +1 -1
- package/dist/components/Markdown/Markdown.module.cjs.map +1 -1
- package/dist/components/Markdown/Markdown.module.js.map +1 -1
- package/dist/components/Menubar/Menubar.module.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.module.js.map +1 -1
- package/dist/components/Modal/Modal.cjs +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js +1 -1
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/Modal/Modal.module.cjs +1 -1
- package/dist/components/Modal/Modal.module.cjs.map +1 -1
- package/dist/components/Modal/Modal.module.js +1 -1
- package/dist/components/Modal/Modal.module.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js +55 -54
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/Navbar/Navbar.cjs +1 -1
- package/dist/components/Navbar/Navbar.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.js +1 -0
- package/dist/components/Navbar/Navbar.js.map +1 -1
- package/dist/components/Navbar/Navbar.module.cjs +1 -1
- package/dist/components/Navbar/Navbar.module.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.module.js +9 -8
- package/dist/components/Navbar/Navbar.module.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.js.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.module.cjs.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.module.js.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.module.cjs.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.module.js.map +1 -1
- package/dist/components/Page/Page.cjs +1 -1
- package/dist/components/Page/Page.cjs.map +1 -1
- package/dist/components/Page/Page.js +23 -39
- package/dist/components/Page/Page.js.map +1 -1
- package/dist/components/Page/Page.module.cjs +1 -1
- package/dist/components/Page/Page.module.cjs.map +1 -1
- package/dist/components/Page/Page.module.js +5 -11
- package/dist/components/Page/Page.module.js.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.module.cjs.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.module.js.map +1 -1
- package/dist/components/PinInput/PinInput.module.cjs.map +1 -1
- package/dist/components/PinInput/PinInput.module.js.map +1 -1
- package/dist/components/Reveal/Reveal.cjs +2 -0
- package/dist/components/Reveal/Reveal.cjs.map +1 -0
- package/dist/components/Reveal/Reveal.js +56 -0
- package/dist/components/Reveal/Reveal.js.map +1 -0
- package/dist/components/Reveal/Reveal.module.cjs +3 -0
- package/dist/components/Reveal/Reveal.module.cjs.map +1 -0
- package/dist/components/Reveal/Reveal.module.js +7 -0
- package/dist/components/Reveal/Reveal.module.js.map +1 -0
- package/dist/components/Reveal/reveal-threshold.cjs +2 -0
- package/dist/components/Reveal/reveal-threshold.cjs.map +1 -0
- package/dist/components/Reveal/reveal-threshold.js +13 -0
- package/dist/components/Reveal/reveal-threshold.js.map +1 -0
- package/dist/components/SectionHeader/SectionHeader.cjs +2 -0
- package/dist/components/SectionHeader/SectionHeader.cjs.map +1 -0
- package/dist/components/SectionHeader/SectionHeader.js +66 -0
- package/dist/components/SectionHeader/SectionHeader.js.map +1 -0
- package/dist/components/SectionHeader/SectionHeader.module.cjs +3 -0
- package/dist/components/SectionHeader/SectionHeader.module.cjs.map +1 -0
- package/dist/components/SectionHeader/SectionHeader.module.js +15 -0
- package/dist/components/SectionHeader/SectionHeader.module.js.map +1 -0
- package/dist/components/Timeline/Timeline.cjs +1 -1
- package/dist/components/Timeline/Timeline.cjs.map +1 -1
- package/dist/components/Timeline/Timeline.js +22 -15
- package/dist/components/Timeline/Timeline.js.map +1 -1
- package/dist/components/Timeline/Timeline.module.cjs +1 -1
- package/dist/components/Timeline/Timeline.module.cjs.map +1 -1
- package/dist/components/Timeline/Timeline.module.js +1 -1
- package/dist/components/Timeline/Timeline.module.js.map +1 -1
- package/dist/components/Tour/Tour.module.cjs.map +1 -1
- package/dist/components/Tour/Tour.module.js.map +1 -1
- package/dist/components/Transfer/Transfer.module.cjs.map +1 -1
- package/dist/components/Transfer/Transfer.module.js.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.module.cjs.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.module.js.map +1 -1
- package/dist/geo/TrajectoryMap.module.cjs.map +1 -1
- package/dist/geo/TrajectoryMap.module.js.map +1 -1
- package/dist/icons/IconPicker.module.cjs.map +1 -1
- package/dist/icons/IconPicker.module.js.map +1 -1
- package/dist/styles/Reveal.css +2 -0
- package/dist/styles/SectionHeader.css +2 -0
- package/dist/styles/actions.css +26 -24
- package/dist/styles/base.css +3 -1
- package/dist/styles/br.css +3 -3
- package/dist/styles/chat.css +5 -5
- package/dist/styles/component/AIChat.css +3 -3
- package/dist/styles/component/AppBar.css +1 -0
- package/dist/styles/component/AppShell.css +1 -1
- package/dist/styles/component/BrazilMap.css +2 -2
- package/dist/styles/component/Button.css +25 -23
- package/dist/styles/component/Carousel.css +3 -3
- package/dist/styles/component/Chat.css +2 -2
- package/dist/styles/component/Dropzone.css +2 -1
- package/dist/styles/component/FilterBar.css +2 -2
- package/dist/styles/component/FloatingActionButton.css +1 -1
- package/dist/styles/component/IconPicker.css +1 -1
- package/dist/styles/component/ImageCropper.css +2 -2
- package/dist/styles/component/ListTile.css +1 -1
- package/dist/styles/component/Markdown.css +1 -1
- package/dist/styles/component/Menubar.css +1 -1
- package/dist/styles/component/Modal.css +2 -2
- package/dist/styles/component/MunicipalitySearch.css +1 -1
- package/dist/styles/component/Navbar.css +1 -0
- package/dist/styles/component/NavigationMenu.css +1 -1
- package/dist/styles/component/NavigationRail.css +1 -1
- package/dist/styles/component/NotificationCenter.css +3 -3
- package/dist/styles/component/Page.css +0 -6
- package/dist/styles/component/PasswordInput.css +1 -1
- package/dist/styles/component/PinInput.css +1 -1
- package/dist/styles/component/Reveal.css +9 -0
- package/dist/styles/component/SectionHeader.css +9 -0
- package/dist/styles/component/Timeline.css +5 -5
- package/dist/styles/component/Tour.css +2 -2
- package/dist/styles/component/TrajectoryMap.css +1 -1
- package/dist/styles/component/Transfer.css +2 -2
- package/dist/styles/component/VirtualTable.css +2 -2
- package/dist/styles/core.css +3 -1
- package/dist/styles/data.css +15 -15
- package/dist/styles/feedback.css +3 -3
- package/dist/styles/forms.css +6 -5
- package/dist/styles/geo.css +1 -1
- package/dist/styles/icons.css +1 -1
- package/dist/styles/inverse.css +372 -0
- package/dist/styles/layout.css +9 -7
- package/dist/styles/manifest.json +14 -1
- package/dist/styles/navigation.css +8 -6
- package/dist/styles/overlay.css +2 -2
- package/dist/styles/utility.css +8 -0
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +473 -11
- package/dist/tempest-react-sdk.js +350 -346
- package/dist/theme/create-theme.cjs +3 -3
- package/dist/theme/create-theme.cjs.map +1 -1
- package/dist/theme/create-theme.js +72 -55
- package/dist/theme/create-theme.js.map +1 -1
- package/dist/theme/inverse-render.cjs +3 -0
- package/dist/theme/inverse-render.cjs.map +1 -0
- package/dist/theme/inverse-render.js +10 -0
- package/dist/theme/inverse-render.js.map +1 -0
- package/dist/theme/inverse-restore.cjs +5 -0
- package/dist/theme/inverse-restore.cjs.map +1 -0
- package/dist/theme/inverse-restore.js +30 -0
- package/dist/theme/inverse-restore.js.map +1 -0
- package/dist/theme/inverse-surface.cjs +2 -0
- package/dist/theme/inverse-surface.cjs.map +1 -0
- package/dist/theme/inverse-surface.js +255 -0
- package/dist/theme/inverse-surface.js.map +1 -0
- package/dist/theme/status-tokens.cjs +2 -0
- package/dist/theme/status-tokens.cjs.map +1 -0
- package/dist/theme/status-tokens.js +15 -0
- package/dist/theme/status-tokens.js.map +1 -0
- package/dist/vite/create-vite-config.cjs +1 -1
- package/dist/vite/create-vite-config.cjs.map +1 -1
- package/dist/vite/create-vite-config.js +6 -4
- package/dist/vite/create-vite-config.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("../Portal/Portal.cjs"),n=require("../Portal/anchor-position.cjs"),r=require("./MultiSelect.module.cjs");let
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../Portal/Portal.cjs"),n=require("../Portal/anchor-position.cjs"),r=require("../Combobox/use-listbox-dismiss.cjs"),i=require("./MultiSelect.module.cjs");let a=require("react"),o=require("react/jsx-runtime");function s(e,t){return!t||e.label.toLowerCase().includes(t.toLowerCase())}var c=4;function l({options:l,value:u,onChange:d,label:f,placeholder:p=`Selecione`,helperText:m,error:h,disabled:g,maxItems:_,filter:v=s,emptyMessage:y=`Nenhuma opção encontrada`,portal:b=!0,className:x}){let S=(0,a.useId)(),C=(0,a.useRef)(null),w=(0,a.useRef)(null),[T,E]=(0,a.useState)(!1),[D,O]=(0,a.useState)(``),[k,A]=(0,a.useState)(0),[j,M]=(0,a.useState)(null),[N,P]=(0,a.useState)(null),F=n.useAnchorPosition({anchor:j,floating:N,side:`bottom`,align:`start`,offset:c,enabled:b&&T,matchWidth:!0}),I=(0,a.useMemo)(()=>new Set(u),[u]),L=(0,a.useMemo)(()=>u.map(e=>l.find(t=>t.value===e)).filter(e=>!!e),[u,l]),R=(0,a.useMemo)(()=>l.filter(e=>v(e,D)),[l,v,D]),z=_!==void 0&&u.length>=_,B=(0,a.useCallback)(e=>{if(!e.disabled){if(I.has(e.value))d(u.filter(t=>t!==e.value));else{if(z)return;d([...u,e.value])}O(``),w.current?.focus()}},[I,d,u,z]),V=(0,a.useCallback)(e=>{d(u.filter(t=>t!==e))},[d,u]),H=(0,a.useCallback)(()=>{E(!1),O(``)},[]),{onInputBlur:U,onListMouseDown:W}=r.useListboxDismiss({open:T,rootRef:C,listNode:N,onDismiss:H}),G=e=>`${S}-option-${e}`,K=T?R[k]:void 0,q=e=>{g||e.target!==e.currentTarget||(e.preventDefault(),w.current?.focus(),E(!0))},J=e=>{if(e.key===`ArrowDown`)e.preventDefault(),E(!0),A(e=>Math.min(R.length-1,e+1));else if(e.key===`ArrowUp`)e.preventDefault(),A(e=>Math.max(0,e-1));else if(e.key===`Enter`){e.preventDefault();let t=R[k];t&&B(t)}else e.key===`Escape`?(T&&e.preventDefault(),H()):e.key===`Backspace`&&D===``&&u.length>0&&V(u[u.length-1])},Y=(0,o.jsx)(`ul`,{ref:P,id:`${S}-listbox`,role:`listbox`,"aria-multiselectable":!0,className:e.cn(i.default.menu,b&&i.default.portalled),style:F,onMouseDown:W,children:R.length===0?(0,o.jsx)(`li`,{className:i.default.empty,children:y}):R.map((t,n)=>{let r=I.has(t.value);return(0,o.jsxs)(`li`,{id:G(n),role:`option`,"aria-selected":r,"aria-disabled":t.disabled||!r&&z,className:e.cn(i.default.option,n===k&&i.default.active,r&&i.default.selected),onMouseEnter:()=>A(n),onMouseDown:()=>B(t),children:[(0,o.jsx)(`span`,{className:i.default.check,"aria-hidden":!0,children:r?`✓`:``}),t.label]},t.value)})});return(0,o.jsxs)(`div`,{ref:C,className:e.cn(i.default.wrapper,h&&i.default.error,x),children:[f&&(0,o.jsx)(`label`,{htmlFor:S,className:i.default.label,children:f}),(0,o.jsxs)(`div`,{ref:M,className:e.cn(i.default.field,g&&i.default.disabled),onMouseDown:q,children:[L.map(e=>(0,o.jsxs)(`span`,{className:i.default.chip,children:[e.label,(0,o.jsx)(`button`,{type:`button`,className:i.default.chipRemove,"aria-label":`Remover ${e.label}`,disabled:g,onMouseDown:t=>{t.preventDefault(),V(e.value)},children:`×`})]},e.value)),(0,o.jsx)(`input`,{id:S,ref:w,type:`text`,role:`combobox`,"aria-expanded":T,"aria-controls":`${S}-listbox`,"aria-autocomplete":`list`,"aria-activedescendant":K?G(k):void 0,className:i.default.input,placeholder:u.length===0?p:``,disabled:g,value:D,onFocus:()=>E(!0),onChange:e=>{O(e.target.value),E(!0),A(0)},onKeyDown:J,onBlur:U})]}),T&&(b?(0,o.jsx)(t.Portal,{children:Y}):Y),h?(0,o.jsx)(`span`,{className:i.default.errorText,children:h}):m?(0,o.jsx)(`span`,{className:i.default.helper,children:m}):null]})}exports.MultiSelect=l;
|
|
2
2
|
//# sourceMappingURL=MultiSelect.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelect.cjs","names":[],"sources":["../../../src/components/MultiSelect/MultiSelect.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — options/value/onChange\n * plus the shared field contract, the knobs that make it multi (maxItems, filter,\n * emptyMessage) and portal. The body is the listbox with chip removal, type-ahead and\n * the ARIA active-descendant wiring reading one active index.\n */\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./MultiSelect.module.css\";\n\nexport interface MultiSelectOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface MultiSelectProps {\n options: MultiSelectOption[];\n /** Currently selected values. */\n value: string[];\n onChange: (value: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n disabled?: boolean;\n /** Cap the number of selectable items. */\n maxItems?: number;\n /** Custom filter — return true to keep the option. Default: case-insensitive substring on label. */\n filter?: (option: MultiSelectOption, query: string) => boolean;\n /** Message shown when no option matches. */\n emptyMessage?: string;\n /**\n * Render the list in a portal, positioned under the field. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the list —\n * in the last row of a `DataTable` none of it was visible (measured in Chrome\n * at 1440 px). In a portal it escapes the clip, takes the field's width,\n * flips above the field when there is no room below, and follows it on\n * scroll, resize and when the field grows a line of chips.\n */\n portal?: boolean;\n className?: string;\n}\n\nfunction defaultFilter(option: MultiSelectOption, query: string): boolean {\n if (!query) return true;\n return option.label.toLowerCase().includes(query.toLowerCase());\n}\n\n/** Gap between field and list, in px — the in-flow CSS uses the same 4 px. */\nconst LIST_OFFSET = 4;\n\n/**\n * MultiSelect — a filterable dropdown that selects many options, shown as\n * removable chips inside the field. Selecting toggles a value; Backspace on an\n * empty query removes the last chip.\n *\n * Keyboard: ArrowUp/ArrowDown navigate, Enter toggles the active option, Esc\n * closes, Backspace (empty input) pops the last chip.\n */\nexport function MultiSelect({\n options,\n value,\n onChange,\n label,\n placeholder = \"Selecione\",\n helperText,\n error,\n disabled,\n maxItems,\n filter = defaultFilter,\n emptyMessage = \"Nenhuma opção encontrada\",\n portal = true,\n className,\n}: MultiSelectProps) {\n const id = useId();\n const rootRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n const [activeIndex, setActiveIndex] = useState(0);\n const [fieldNode, setFieldNode] = useState<HTMLDivElement | null>(null);\n const [listNode, setListNode] = useState<HTMLUListElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: fieldNode,\n floating: listNode,\n side: \"bottom\",\n align: \"start\",\n offset: LIST_OFFSET,\n enabled: portal && open,\n matchWidth: true,\n });\n\n const selectedSet = useMemo(() => new Set(value), [value]);\n const selectedOptions = useMemo(\n () =>\n value\n .map((v) => options.find((o) => o.value === v))\n .filter((o): o is MultiSelectOption => Boolean(o)),\n [value, options],\n );\n const filtered = useMemo(\n () => options.filter((o) => filter(o, query)),\n [options, filter, query],\n );\n\n const atMax = maxItems !== undefined && value.length >= maxItems;\n\n const toggle = useCallback(\n (option: MultiSelectOption): void => {\n if (option.disabled) return;\n if (selectedSet.has(option.value)) {\n onChange(value.filter((v) => v !== option.value));\n } else {\n if (atMax) return;\n onChange([...value, option.value]);\n }\n setQuery(\"\");\n inputRef.current?.focus();\n },\n [selectedSet, onChange, value, atMax],\n );\n\n const removeAt = useCallback(\n (optionValue: string): void => {\n onChange(value.filter((v) => v !== optionValue));\n },\n [onChange, value],\n );\n\n /**\n * Close on a press outside both the field and the list.\n *\n * The list is checked on its own because in a portal it is not inside the\n * field's wrapper: a press on its scrollbar, or on the empty message, would\n * otherwise count as outside and close it.\n */\n useEffect(() => {\n if (!open) return;\n const onDown = (event: MouseEvent): void => {\n const target = event.target as Node;\n if (rootRef.current?.contains(target) || listNode?.contains(target)) return;\n setOpen(false);\n setQuery(\"\");\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => window.removeEventListener(\"mousedown\", onDown);\n }, [open, listNode]);\n\n /**\n * Open the list and keep focus on the input when the empty part of the field\n * is pressed.\n *\n * Only a press on the field itself counts: a press on a chip or on its remove\n * button has its own handler, and the input focuses itself.\n */\n const handleFieldMouseDown = (event: React.MouseEvent<HTMLDivElement>): void => {\n if (disabled || event.target !== event.currentTarget) return;\n event.preventDefault();\n inputRef.current?.focus();\n setOpen(true);\n };\n\n /**\n * Keyboard model of the input.\n *\n * `Escape` calls `preventDefault()` only while the list is open: that marks the\n * key as consumed, so an enclosing `Modal` or `Drawer` stays open and the next\n * `Escape` reaches it. With the list closed the key is left alone and dismisses\n * the enclosing layer, as the user expects.\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setOpen(true);\n setActiveIndex((current) => Math.min(filtered.length - 1, current + 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((current) => Math.max(0, current - 1));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const option = filtered[activeIndex];\n if (option) toggle(option);\n } else if (event.key === \"Escape\") {\n if (open) event.preventDefault();\n setOpen(false);\n setQuery(\"\");\n } else if (event.key === \"Backspace\" && query === \"\" && value.length > 0) {\n removeAt(value[value.length - 1]);\n }\n };\n\n const list = (\n <ul\n ref={setListNode}\n id={`${id}-listbox`}\n role=\"listbox\"\n aria-multiselectable\n className={cn(styles.menu, portal && styles.portalled)}\n style={floatingStyle}\n >\n {filtered.length === 0 ? (\n <li className={styles.empty}>{emptyMessage}</li>\n ) : (\n filtered.map((option, index) => {\n const isSelected = selectedSet.has(option.value);\n return (\n <li\n key={option.value}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={option.disabled || (!isSelected && atMax)}\n className={cn(\n styles.option,\n index === activeIndex && styles.active,\n isSelected && styles.selected,\n )}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseDown={(event) => {\n event.preventDefault();\n toggle(option);\n }}\n >\n <span className={styles.check} aria-hidden>\n {isSelected ? \"✓\" : \"\"}\n </span>\n {option.label}\n </li>\n );\n })\n )}\n </ul>\n );\n\n return (\n <div ref={rootRef} className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={id} className={styles.label}>\n {label}\n </label>\n )}\n <div\n ref={setFieldNode}\n className={cn(styles.field, disabled && styles.disabled)}\n onMouseDown={handleFieldMouseDown}\n >\n {selectedOptions.map((option) => (\n <span key={option.value} className={styles.chip}>\n {option.label}\n <button\n type=\"button\"\n className={styles.chipRemove}\n aria-label={`Remover ${option.label}`}\n disabled={disabled}\n onMouseDown={(event) => {\n event.preventDefault();\n removeAt(option.value);\n }}\n >\n ×\n </button>\n </span>\n ))}\n <input\n id={id}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={`${id}-listbox`}\n aria-autocomplete=\"list\"\n className={styles.input}\n placeholder={value.length === 0 ? placeholder : \"\"}\n disabled={disabled}\n value={query}\n onFocus={() => setOpen(true)}\n onChange={(event) => {\n setQuery(event.target.value);\n setOpen(true);\n setActiveIndex(0);\n }}\n onKeyDown={handleKeyDown}\n />\n </div>\n {open && (portal ? <Portal>{list}</Portal> : list)}\n {error ? (\n <span className={styles.errorText}>{error}</span>\n ) : helperText ? (\n <span className={styles.helper}>{helperText}</span>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MultiSelect.cjs","names":[],"sources":["../../../src/components/MultiSelect/MultiSelect.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — options/value/onChange\n * plus the shared field contract, the knobs that make it multi (maxItems, filter,\n * emptyMessage) and portal. The body is the listbox with chip removal, type-ahead and\n * the ARIA active-descendant wiring reading one active index.\n */\nimport { useCallback, useId, useMemo, useRef, useState } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useListboxDismiss } from \"@/components/Combobox/use-listbox-dismiss\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./MultiSelect.module.css\";\n\nexport interface MultiSelectOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface MultiSelectProps {\n options: MultiSelectOption[];\n /** Currently selected values. */\n value: string[];\n onChange: (value: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n disabled?: boolean;\n /** Cap the number of selectable items. */\n maxItems?: number;\n /** Custom filter — return true to keep the option. Default: case-insensitive substring on label. */\n filter?: (option: MultiSelectOption, query: string) => boolean;\n /** Message shown when no option matches. */\n emptyMessage?: string;\n /**\n * Render the list in a portal, positioned under the field. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the list —\n * in the last row of a `DataTable` none of it was visible (measured in Chrome\n * at 1440 px). In a portal it escapes the clip, takes the field's width,\n * flips above the field when there is no room below, and follows it on\n * scroll, resize and when the field grows a line of chips.\n */\n portal?: boolean;\n className?: string;\n}\n\nfunction defaultFilter(option: MultiSelectOption, query: string): boolean {\n if (!query) return true;\n return option.label.toLowerCase().includes(query.toLowerCase());\n}\n\n/** Gap between field and list, in px — the in-flow CSS uses the same 4 px. */\nconst LIST_OFFSET = 4;\n\n/**\n * MultiSelect — a filterable dropdown that selects many options, shown as\n * removable chips inside the field. Selecting toggles a value; Backspace on an\n * empty query removes the last chip.\n *\n * Keyboard: ArrowUp/ArrowDown navigate — the active option is announced through\n * `aria-activedescendant`, focus stays on the input — Enter toggles the active\n * option, Esc closes, Backspace (empty input) pops the last chip, and\n * Tab/Shift+Tab leave the field closing the list.\n */\nexport function MultiSelect({\n options,\n value,\n onChange,\n label,\n placeholder = \"Selecione\",\n helperText,\n error,\n disabled,\n maxItems,\n filter = defaultFilter,\n emptyMessage = \"Nenhuma opção encontrada\",\n portal = true,\n className,\n}: MultiSelectProps) {\n const id = useId();\n const rootRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n const [activeIndex, setActiveIndex] = useState(0);\n const [fieldNode, setFieldNode] = useState<HTMLDivElement | null>(null);\n const [listNode, setListNode] = useState<HTMLUListElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: fieldNode,\n floating: listNode,\n side: \"bottom\",\n align: \"start\",\n offset: LIST_OFFSET,\n enabled: portal && open,\n matchWidth: true,\n });\n\n const selectedSet = useMemo(() => new Set(value), [value]);\n const selectedOptions = useMemo(\n () =>\n value\n .map((v) => options.find((o) => o.value === v))\n .filter((o): o is MultiSelectOption => Boolean(o)),\n [value, options],\n );\n const filtered = useMemo(\n () => options.filter((o) => filter(o, query)),\n [options, filter, query],\n );\n\n const atMax = maxItems !== undefined && value.length >= maxItems;\n\n const toggle = useCallback(\n (option: MultiSelectOption): void => {\n if (option.disabled) return;\n if (selectedSet.has(option.value)) {\n onChange(value.filter((v) => v !== option.value));\n } else {\n if (atMax) return;\n onChange([...value, option.value]);\n }\n setQuery(\"\");\n inputRef.current?.focus();\n },\n [selectedSet, onChange, value, atMax],\n );\n\n const removeAt = useCallback(\n (optionValue: string): void => {\n onChange(value.filter((v) => v !== optionValue));\n },\n [onChange, value],\n );\n\n const close = useCallback((): void => {\n setOpen(false);\n setQuery(\"\");\n }, []);\n\n const { onInputBlur, onListMouseDown } = useListboxDismiss({\n open,\n rootRef,\n listNode,\n onDismiss: close,\n });\n const optionId = (index: number): string => `${id}-option-${index}`;\n const activeOption = open ? filtered[activeIndex] : undefined;\n\n /**\n * Open the list and keep focus on the input when the empty part of the field\n * is pressed.\n *\n * Only a press on the field itself counts: a press on a chip or on its remove\n * button has its own handler, and the input focuses itself.\n */\n const handleFieldMouseDown = (event: React.MouseEvent<HTMLDivElement>): void => {\n if (disabled || event.target !== event.currentTarget) return;\n event.preventDefault();\n inputRef.current?.focus();\n setOpen(true);\n };\n\n /**\n * Keyboard model of the input.\n *\n * `Escape` calls `preventDefault()` only while the list is open: that marks the\n * key as consumed, so an enclosing `Modal` or `Drawer` stays open and the next\n * `Escape` reaches it. With the list closed the key is left alone and dismisses\n * the enclosing layer, as the user expects.\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setOpen(true);\n setActiveIndex((current) => Math.min(filtered.length - 1, current + 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((current) => Math.max(0, current - 1));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const option = filtered[activeIndex];\n if (option) toggle(option);\n } else if (event.key === \"Escape\") {\n if (open) event.preventDefault();\n close();\n } else if (event.key === \"Backspace\" && query === \"\" && value.length > 0) {\n removeAt(value[value.length - 1]);\n }\n };\n\n const list = (\n <ul\n ref={setListNode}\n id={`${id}-listbox`}\n role=\"listbox\"\n aria-multiselectable\n className={cn(styles.menu, portal && styles.portalled)}\n style={floatingStyle}\n onMouseDown={onListMouseDown}\n >\n {filtered.length === 0 ? (\n <li className={styles.empty}>{emptyMessage}</li>\n ) : (\n filtered.map((option, index) => {\n const isSelected = selectedSet.has(option.value);\n return (\n <li\n key={option.value}\n id={optionId(index)}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={option.disabled || (!isSelected && atMax)}\n className={cn(\n styles.option,\n index === activeIndex && styles.active,\n isSelected && styles.selected,\n )}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseDown={() => toggle(option)}\n >\n <span className={styles.check} aria-hidden>\n {isSelected ? \"✓\" : \"\"}\n </span>\n {option.label}\n </li>\n );\n })\n )}\n </ul>\n );\n\n return (\n <div ref={rootRef} className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={id} className={styles.label}>\n {label}\n </label>\n )}\n <div\n ref={setFieldNode}\n className={cn(styles.field, disabled && styles.disabled)}\n onMouseDown={handleFieldMouseDown}\n >\n {selectedOptions.map((option) => (\n <span key={option.value} className={styles.chip}>\n {option.label}\n <button\n type=\"button\"\n className={styles.chipRemove}\n aria-label={`Remover ${option.label}`}\n disabled={disabled}\n onMouseDown={(event) => {\n event.preventDefault();\n removeAt(option.value);\n }}\n >\n ×\n </button>\n </span>\n ))}\n <input\n id={id}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={`${id}-listbox`}\n aria-autocomplete=\"list\"\n aria-activedescendant={activeOption ? optionId(activeIndex) : undefined}\n className={styles.input}\n placeholder={value.length === 0 ? placeholder : \"\"}\n disabled={disabled}\n value={query}\n onFocus={() => setOpen(true)}\n onChange={(event) => {\n setQuery(event.target.value);\n setOpen(true);\n setActiveIndex(0);\n }}\n onKeyDown={handleKeyDown}\n onBlur={onInputBlur}\n />\n </div>\n {open && (portal ? <Portal>{list}</Portal> : list)}\n {error ? (\n <span className={styles.errorText}>{error}</span>\n ) : helperText ? (\n <span className={styles.helper}>{helperText}</span>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
2
|
import { Portal as t } from "../Portal/Portal.js";
|
|
3
3
|
import { useAnchorPosition as n } from "../Portal/anchor-position.js";
|
|
4
|
-
import r from "
|
|
5
|
-
import
|
|
4
|
+
import { useListboxDismiss as r } from "../Combobox/use-listbox-dismiss.js";
|
|
5
|
+
import i from "./MultiSelect.module.js";
|
|
6
|
+
import { useCallback as a, useId as o, useMemo as s, useRef as c, useState as l } from "react";
|
|
6
7
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
8
|
//#region src/components/MultiSelect/MultiSelect.tsx
|
|
8
9
|
function f(e, t) {
|
|
@@ -10,7 +11,7 @@ function f(e, t) {
|
|
|
10
11
|
}
|
|
11
12
|
var p = 4;
|
|
12
13
|
function m({ options: m, value: h, onChange: g, label: _, placeholder: v = "Selecione", helperText: y, error: b, disabled: x, maxItems: S, filter: C = f, emptyMessage: w = "Nenhuma opção encontrada", portal: T = !0, className: E }) {
|
|
13
|
-
let D = o(), O = c(null), k = c(null), [A, j] = l(!1), [M, N] = l(""), [P, F] = l(0), [I, L] = l(null), [R,
|
|
14
|
+
let D = o(), O = c(null), k = c(null), [A, j] = l(!1), [M, N] = l(""), [P, F] = l(0), [I, L] = l(null), [R, ee] = l(null), z = n({
|
|
14
15
|
anchor: I,
|
|
15
16
|
floating: R,
|
|
16
17
|
side: "bottom",
|
|
@@ -18,96 +19,94 @@ function m({ options: m, value: h, onChange: g, label: _, placeholder: v = "Sele
|
|
|
18
19
|
offset: p,
|
|
19
20
|
enabled: T && A,
|
|
20
21
|
matchWidth: !0
|
|
21
|
-
}),
|
|
22
|
+
}), B = s(() => new Set(h), [h]), V = s(() => h.map((e) => m.find((t) => t.value === e)).filter((e) => !!e), [h, m]), H = s(() => m.filter((e) => C(e, M)), [
|
|
22
23
|
m,
|
|
23
24
|
C,
|
|
24
25
|
M
|
|
25
|
-
]),
|
|
26
|
+
]), U = S !== void 0 && h.length >= S, W = a((e) => {
|
|
26
27
|
if (!e.disabled) {
|
|
27
|
-
if (
|
|
28
|
+
if (B.has(e.value)) g(h.filter((t) => t !== e.value));
|
|
28
29
|
else {
|
|
29
|
-
if (
|
|
30
|
+
if (U) return;
|
|
30
31
|
g([...h, e.value]);
|
|
31
32
|
}
|
|
32
33
|
N(""), k.current?.focus();
|
|
33
34
|
}
|
|
34
35
|
}, [
|
|
35
|
-
|
|
36
|
+
B,
|
|
36
37
|
g,
|
|
37
38
|
h,
|
|
38
|
-
|
|
39
|
-
]),
|
|
39
|
+
U
|
|
40
|
+
]), G = a((e) => {
|
|
40
41
|
g(h.filter((t) => t !== e));
|
|
41
|
-
}, [g, h])
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}, [A, R]);
|
|
50
|
-
let q = (e) => {
|
|
42
|
+
}, [g, h]), K = a(() => {
|
|
43
|
+
j(!1), N("");
|
|
44
|
+
}, []), { onInputBlur: q, onListMouseDown: J } = r({
|
|
45
|
+
open: A,
|
|
46
|
+
rootRef: O,
|
|
47
|
+
listNode: R,
|
|
48
|
+
onDismiss: K
|
|
49
|
+
}), Y = (e) => `${D}-option-${e}`, X = A ? H[P] : void 0, Z = (e) => {
|
|
51
50
|
x || e.target !== e.currentTarget || (e.preventDefault(), k.current?.focus(), j(!0));
|
|
52
|
-
},
|
|
53
|
-
if (e.key === "ArrowDown") e.preventDefault(), j(!0), F((e) => Math.min(
|
|
51
|
+
}, Q = (e) => {
|
|
52
|
+
if (e.key === "ArrowDown") e.preventDefault(), j(!0), F((e) => Math.min(H.length - 1, e + 1));
|
|
54
53
|
else if (e.key === "ArrowUp") e.preventDefault(), F((e) => Math.max(0, e - 1));
|
|
55
54
|
else if (e.key === "Enter") {
|
|
56
55
|
e.preventDefault();
|
|
57
|
-
let t =
|
|
58
|
-
t &&
|
|
59
|
-
} else e.key === "Escape" ? (A && e.preventDefault(),
|
|
60
|
-
},
|
|
61
|
-
ref:
|
|
56
|
+
let t = H[P];
|
|
57
|
+
t && W(t);
|
|
58
|
+
} else e.key === "Escape" ? (A && e.preventDefault(), K()) : e.key === "Backspace" && M === "" && h.length > 0 && G(h[h.length - 1]);
|
|
59
|
+
}, $ = /* @__PURE__ */ u("ul", {
|
|
60
|
+
ref: ee,
|
|
62
61
|
id: `${D}-listbox`,
|
|
63
62
|
role: "listbox",
|
|
64
63
|
"aria-multiselectable": !0,
|
|
65
|
-
className: e(
|
|
66
|
-
style:
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
className: e(i.menu, T && i.portalled),
|
|
65
|
+
style: z,
|
|
66
|
+
onMouseDown: J,
|
|
67
|
+
children: H.length === 0 ? /* @__PURE__ */ u("li", {
|
|
68
|
+
className: i.empty,
|
|
69
69
|
children: w
|
|
70
|
-
}) :
|
|
71
|
-
let
|
|
70
|
+
}) : H.map((t, n) => {
|
|
71
|
+
let r = B.has(t.value);
|
|
72
72
|
return /* @__PURE__ */ d("li", {
|
|
73
|
+
id: Y(n),
|
|
73
74
|
role: "option",
|
|
74
|
-
"aria-selected":
|
|
75
|
-
"aria-disabled": t.disabled || !
|
|
76
|
-
className: e(
|
|
75
|
+
"aria-selected": r,
|
|
76
|
+
"aria-disabled": t.disabled || !r && U,
|
|
77
|
+
className: e(i.option, n === P && i.active, r && i.selected),
|
|
77
78
|
onMouseEnter: () => F(n),
|
|
78
|
-
onMouseDown: (
|
|
79
|
-
e.preventDefault(), G(t);
|
|
80
|
-
},
|
|
79
|
+
onMouseDown: () => W(t),
|
|
81
80
|
children: [/* @__PURE__ */ u("span", {
|
|
82
|
-
className:
|
|
81
|
+
className: i.check,
|
|
83
82
|
"aria-hidden": !0,
|
|
84
|
-
children:
|
|
83
|
+
children: r ? "✓" : ""
|
|
85
84
|
}), t.label]
|
|
86
85
|
}, t.value);
|
|
87
86
|
})
|
|
88
87
|
});
|
|
89
88
|
return /* @__PURE__ */ d("div", {
|
|
90
89
|
ref: O,
|
|
91
|
-
className: e(
|
|
90
|
+
className: e(i.wrapper, b && i.error, E),
|
|
92
91
|
children: [
|
|
93
92
|
_ && /* @__PURE__ */ u("label", {
|
|
94
93
|
htmlFor: D,
|
|
95
|
-
className:
|
|
94
|
+
className: i.label,
|
|
96
95
|
children: _
|
|
97
96
|
}),
|
|
98
97
|
/* @__PURE__ */ d("div", {
|
|
99
98
|
ref: L,
|
|
100
|
-
className: e(
|
|
101
|
-
onMouseDown:
|
|
102
|
-
children: [
|
|
103
|
-
className:
|
|
99
|
+
className: e(i.field, x && i.disabled),
|
|
100
|
+
onMouseDown: Z,
|
|
101
|
+
children: [V.map((e) => /* @__PURE__ */ d("span", {
|
|
102
|
+
className: i.chip,
|
|
104
103
|
children: [e.label, /* @__PURE__ */ u("button", {
|
|
105
104
|
type: "button",
|
|
106
|
-
className:
|
|
105
|
+
className: i.chipRemove,
|
|
107
106
|
"aria-label": `Remover ${e.label}`,
|
|
108
107
|
disabled: x,
|
|
109
108
|
onMouseDown: (t) => {
|
|
110
|
-
t.preventDefault(),
|
|
109
|
+
t.preventDefault(), G(e.value);
|
|
111
110
|
},
|
|
112
111
|
children: "×"
|
|
113
112
|
})]
|
|
@@ -119,7 +118,8 @@ function m({ options: m, value: h, onChange: g, label: _, placeholder: v = "Sele
|
|
|
119
118
|
"aria-expanded": A,
|
|
120
119
|
"aria-controls": `${D}-listbox`,
|
|
121
120
|
"aria-autocomplete": "list",
|
|
122
|
-
|
|
121
|
+
"aria-activedescendant": X ? Y(P) : void 0,
|
|
122
|
+
className: i.input,
|
|
123
123
|
placeholder: h.length === 0 ? v : "",
|
|
124
124
|
disabled: x,
|
|
125
125
|
value: M,
|
|
@@ -127,15 +127,16 @@ function m({ options: m, value: h, onChange: g, label: _, placeholder: v = "Sele
|
|
|
127
127
|
onChange: (e) => {
|
|
128
128
|
N(e.target.value), j(!0), F(0);
|
|
129
129
|
},
|
|
130
|
-
onKeyDown:
|
|
130
|
+
onKeyDown: Q,
|
|
131
|
+
onBlur: q
|
|
131
132
|
})]
|
|
132
133
|
}),
|
|
133
|
-
A && (T ? /* @__PURE__ */ u(t, { children:
|
|
134
|
+
A && (T ? /* @__PURE__ */ u(t, { children: $ }) : $),
|
|
134
135
|
b ? /* @__PURE__ */ u("span", {
|
|
135
|
-
className:
|
|
136
|
+
className: i.errorText,
|
|
136
137
|
children: b
|
|
137
138
|
}) : y ? /* @__PURE__ */ u("span", {
|
|
138
|
-
className:
|
|
139
|
+
className: i.helper,
|
|
139
140
|
children: y
|
|
140
141
|
}) : null
|
|
141
142
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelect.js","names":[],"sources":["../../../src/components/MultiSelect/MultiSelect.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — options/value/onChange\n * plus the shared field contract, the knobs that make it multi (maxItems, filter,\n * emptyMessage) and portal. The body is the listbox with chip removal, type-ahead and\n * the ARIA active-descendant wiring reading one active index.\n */\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./MultiSelect.module.css\";\n\nexport interface MultiSelectOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface MultiSelectProps {\n options: MultiSelectOption[];\n /** Currently selected values. */\n value: string[];\n onChange: (value: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n disabled?: boolean;\n /** Cap the number of selectable items. */\n maxItems?: number;\n /** Custom filter — return true to keep the option. Default: case-insensitive substring on label. */\n filter?: (option: MultiSelectOption, query: string) => boolean;\n /** Message shown when no option matches. */\n emptyMessage?: string;\n /**\n * Render the list in a portal, positioned under the field. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the list —\n * in the last row of a `DataTable` none of it was visible (measured in Chrome\n * at 1440 px). In a portal it escapes the clip, takes the field's width,\n * flips above the field when there is no room below, and follows it on\n * scroll, resize and when the field grows a line of chips.\n */\n portal?: boolean;\n className?: string;\n}\n\nfunction defaultFilter(option: MultiSelectOption, query: string): boolean {\n if (!query) return true;\n return option.label.toLowerCase().includes(query.toLowerCase());\n}\n\n/** Gap between field and list, in px — the in-flow CSS uses the same 4 px. */\nconst LIST_OFFSET = 4;\n\n/**\n * MultiSelect — a filterable dropdown that selects many options, shown as\n * removable chips inside the field. Selecting toggles a value; Backspace on an\n * empty query removes the last chip.\n *\n * Keyboard: ArrowUp/ArrowDown navigate, Enter toggles the active option, Esc\n * closes, Backspace (empty input) pops the last chip.\n */\nexport function MultiSelect({\n options,\n value,\n onChange,\n label,\n placeholder = \"Selecione\",\n helperText,\n error,\n disabled,\n maxItems,\n filter = defaultFilter,\n emptyMessage = \"Nenhuma opção encontrada\",\n portal = true,\n className,\n}: MultiSelectProps) {\n const id = useId();\n const rootRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n const [activeIndex, setActiveIndex] = useState(0);\n const [fieldNode, setFieldNode] = useState<HTMLDivElement | null>(null);\n const [listNode, setListNode] = useState<HTMLUListElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: fieldNode,\n floating: listNode,\n side: \"bottom\",\n align: \"start\",\n offset: LIST_OFFSET,\n enabled: portal && open,\n matchWidth: true,\n });\n\n const selectedSet = useMemo(() => new Set(value), [value]);\n const selectedOptions = useMemo(\n () =>\n value\n .map((v) => options.find((o) => o.value === v))\n .filter((o): o is MultiSelectOption => Boolean(o)),\n [value, options],\n );\n const filtered = useMemo(\n () => options.filter((o) => filter(o, query)),\n [options, filter, query],\n );\n\n const atMax = maxItems !== undefined && value.length >= maxItems;\n\n const toggle = useCallback(\n (option: MultiSelectOption): void => {\n if (option.disabled) return;\n if (selectedSet.has(option.value)) {\n onChange(value.filter((v) => v !== option.value));\n } else {\n if (atMax) return;\n onChange([...value, option.value]);\n }\n setQuery(\"\");\n inputRef.current?.focus();\n },\n [selectedSet, onChange, value, atMax],\n );\n\n const removeAt = useCallback(\n (optionValue: string): void => {\n onChange(value.filter((v) => v !== optionValue));\n },\n [onChange, value],\n );\n\n /**\n * Close on a press outside both the field and the list.\n *\n * The list is checked on its own because in a portal it is not inside the\n * field's wrapper: a press on its scrollbar, or on the empty message, would\n * otherwise count as outside and close it.\n */\n useEffect(() => {\n if (!open) return;\n const onDown = (event: MouseEvent): void => {\n const target = event.target as Node;\n if (rootRef.current?.contains(target) || listNode?.contains(target)) return;\n setOpen(false);\n setQuery(\"\");\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => window.removeEventListener(\"mousedown\", onDown);\n }, [open, listNode]);\n\n /**\n * Open the list and keep focus on the input when the empty part of the field\n * is pressed.\n *\n * Only a press on the field itself counts: a press on a chip or on its remove\n * button has its own handler, and the input focuses itself.\n */\n const handleFieldMouseDown = (event: React.MouseEvent<HTMLDivElement>): void => {\n if (disabled || event.target !== event.currentTarget) return;\n event.preventDefault();\n inputRef.current?.focus();\n setOpen(true);\n };\n\n /**\n * Keyboard model of the input.\n *\n * `Escape` calls `preventDefault()` only while the list is open: that marks the\n * key as consumed, so an enclosing `Modal` or `Drawer` stays open and the next\n * `Escape` reaches it. With the list closed the key is left alone and dismisses\n * the enclosing layer, as the user expects.\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setOpen(true);\n setActiveIndex((current) => Math.min(filtered.length - 1, current + 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((current) => Math.max(0, current - 1));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const option = filtered[activeIndex];\n if (option) toggle(option);\n } else if (event.key === \"Escape\") {\n if (open) event.preventDefault();\n setOpen(false);\n setQuery(\"\");\n } else if (event.key === \"Backspace\" && query === \"\" && value.length > 0) {\n removeAt(value[value.length - 1]);\n }\n };\n\n const list = (\n <ul\n ref={setListNode}\n id={`${id}-listbox`}\n role=\"listbox\"\n aria-multiselectable\n className={cn(styles.menu, portal && styles.portalled)}\n style={floatingStyle}\n >\n {filtered.length === 0 ? (\n <li className={styles.empty}>{emptyMessage}</li>\n ) : (\n filtered.map((option, index) => {\n const isSelected = selectedSet.has(option.value);\n return (\n <li\n key={option.value}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={option.disabled || (!isSelected && atMax)}\n className={cn(\n styles.option,\n index === activeIndex && styles.active,\n isSelected && styles.selected,\n )}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseDown={(event) => {\n event.preventDefault();\n toggle(option);\n }}\n >\n <span className={styles.check} aria-hidden>\n {isSelected ? \"✓\" : \"\"}\n </span>\n {option.label}\n </li>\n );\n })\n )}\n </ul>\n );\n\n return (\n <div ref={rootRef} className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={id} className={styles.label}>\n {label}\n </label>\n )}\n <div\n ref={setFieldNode}\n className={cn(styles.field, disabled && styles.disabled)}\n onMouseDown={handleFieldMouseDown}\n >\n {selectedOptions.map((option) => (\n <span key={option.value} className={styles.chip}>\n {option.label}\n <button\n type=\"button\"\n className={styles.chipRemove}\n aria-label={`Remover ${option.label}`}\n disabled={disabled}\n onMouseDown={(event) => {\n event.preventDefault();\n removeAt(option.value);\n }}\n >\n ×\n </button>\n </span>\n ))}\n <input\n id={id}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={`${id}-listbox`}\n aria-autocomplete=\"list\"\n className={styles.input}\n placeholder={value.length === 0 ? placeholder : \"\"}\n disabled={disabled}\n value={query}\n onFocus={() => setOpen(true)}\n onChange={(event) => {\n setQuery(event.target.value);\n setOpen(true);\n setActiveIndex(0);\n }}\n onKeyDown={handleKeyDown}\n />\n </div>\n {open && (portal ? <Portal>{list}</Portal> : list)}\n {error ? (\n <span className={styles.errorText}>{error}</span>\n ) : helperText ? (\n <span className={styles.helper}>{helperText}</span>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MultiSelect.js","names":[],"sources":["../../../src/components/MultiSelect/MultiSelect.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — options/value/onChange\n * plus the shared field contract, the knobs that make it multi (maxItems, filter,\n * emptyMessage) and portal. The body is the listbox with chip removal, type-ahead and\n * the ARIA active-descendant wiring reading one active index.\n */\nimport { useCallback, useId, useMemo, useRef, useState } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useListboxDismiss } from \"@/components/Combobox/use-listbox-dismiss\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./MultiSelect.module.css\";\n\nexport interface MultiSelectOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface MultiSelectProps {\n options: MultiSelectOption[];\n /** Currently selected values. */\n value: string[];\n onChange: (value: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n disabled?: boolean;\n /** Cap the number of selectable items. */\n maxItems?: number;\n /** Custom filter — return true to keep the option. Default: case-insensitive substring on label. */\n filter?: (option: MultiSelectOption, query: string) => boolean;\n /** Message shown when no option matches. */\n emptyMessage?: string;\n /**\n * Render the list in a portal, positioned under the field. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the list —\n * in the last row of a `DataTable` none of it was visible (measured in Chrome\n * at 1440 px). In a portal it escapes the clip, takes the field's width,\n * flips above the field when there is no room below, and follows it on\n * scroll, resize and when the field grows a line of chips.\n */\n portal?: boolean;\n className?: string;\n}\n\nfunction defaultFilter(option: MultiSelectOption, query: string): boolean {\n if (!query) return true;\n return option.label.toLowerCase().includes(query.toLowerCase());\n}\n\n/** Gap between field and list, in px — the in-flow CSS uses the same 4 px. */\nconst LIST_OFFSET = 4;\n\n/**\n * MultiSelect — a filterable dropdown that selects many options, shown as\n * removable chips inside the field. Selecting toggles a value; Backspace on an\n * empty query removes the last chip.\n *\n * Keyboard: ArrowUp/ArrowDown navigate — the active option is announced through\n * `aria-activedescendant`, focus stays on the input — Enter toggles the active\n * option, Esc closes, Backspace (empty input) pops the last chip, and\n * Tab/Shift+Tab leave the field closing the list.\n */\nexport function MultiSelect({\n options,\n value,\n onChange,\n label,\n placeholder = \"Selecione\",\n helperText,\n error,\n disabled,\n maxItems,\n filter = defaultFilter,\n emptyMessage = \"Nenhuma opção encontrada\",\n portal = true,\n className,\n}: MultiSelectProps) {\n const id = useId();\n const rootRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n const [activeIndex, setActiveIndex] = useState(0);\n const [fieldNode, setFieldNode] = useState<HTMLDivElement | null>(null);\n const [listNode, setListNode] = useState<HTMLUListElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: fieldNode,\n floating: listNode,\n side: \"bottom\",\n align: \"start\",\n offset: LIST_OFFSET,\n enabled: portal && open,\n matchWidth: true,\n });\n\n const selectedSet = useMemo(() => new Set(value), [value]);\n const selectedOptions = useMemo(\n () =>\n value\n .map((v) => options.find((o) => o.value === v))\n .filter((o): o is MultiSelectOption => Boolean(o)),\n [value, options],\n );\n const filtered = useMemo(\n () => options.filter((o) => filter(o, query)),\n [options, filter, query],\n );\n\n const atMax = maxItems !== undefined && value.length >= maxItems;\n\n const toggle = useCallback(\n (option: MultiSelectOption): void => {\n if (option.disabled) return;\n if (selectedSet.has(option.value)) {\n onChange(value.filter((v) => v !== option.value));\n } else {\n if (atMax) return;\n onChange([...value, option.value]);\n }\n setQuery(\"\");\n inputRef.current?.focus();\n },\n [selectedSet, onChange, value, atMax],\n );\n\n const removeAt = useCallback(\n (optionValue: string): void => {\n onChange(value.filter((v) => v !== optionValue));\n },\n [onChange, value],\n );\n\n const close = useCallback((): void => {\n setOpen(false);\n setQuery(\"\");\n }, []);\n\n const { onInputBlur, onListMouseDown } = useListboxDismiss({\n open,\n rootRef,\n listNode,\n onDismiss: close,\n });\n const optionId = (index: number): string => `${id}-option-${index}`;\n const activeOption = open ? filtered[activeIndex] : undefined;\n\n /**\n * Open the list and keep focus on the input when the empty part of the field\n * is pressed.\n *\n * Only a press on the field itself counts: a press on a chip or on its remove\n * button has its own handler, and the input focuses itself.\n */\n const handleFieldMouseDown = (event: React.MouseEvent<HTMLDivElement>): void => {\n if (disabled || event.target !== event.currentTarget) return;\n event.preventDefault();\n inputRef.current?.focus();\n setOpen(true);\n };\n\n /**\n * Keyboard model of the input.\n *\n * `Escape` calls `preventDefault()` only while the list is open: that marks the\n * key as consumed, so an enclosing `Modal` or `Drawer` stays open and the next\n * `Escape` reaches it. With the list closed the key is left alone and dismisses\n * the enclosing layer, as the user expects.\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setOpen(true);\n setActiveIndex((current) => Math.min(filtered.length - 1, current + 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((current) => Math.max(0, current - 1));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const option = filtered[activeIndex];\n if (option) toggle(option);\n } else if (event.key === \"Escape\") {\n if (open) event.preventDefault();\n close();\n } else if (event.key === \"Backspace\" && query === \"\" && value.length > 0) {\n removeAt(value[value.length - 1]);\n }\n };\n\n const list = (\n <ul\n ref={setListNode}\n id={`${id}-listbox`}\n role=\"listbox\"\n aria-multiselectable\n className={cn(styles.menu, portal && styles.portalled)}\n style={floatingStyle}\n onMouseDown={onListMouseDown}\n >\n {filtered.length === 0 ? (\n <li className={styles.empty}>{emptyMessage}</li>\n ) : (\n filtered.map((option, index) => {\n const isSelected = selectedSet.has(option.value);\n return (\n <li\n key={option.value}\n id={optionId(index)}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={option.disabled || (!isSelected && atMax)}\n className={cn(\n styles.option,\n index === activeIndex && styles.active,\n isSelected && styles.selected,\n )}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseDown={() => toggle(option)}\n >\n <span className={styles.check} aria-hidden>\n {isSelected ? \"✓\" : \"\"}\n </span>\n {option.label}\n </li>\n );\n })\n )}\n </ul>\n );\n\n return (\n <div ref={rootRef} className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={id} className={styles.label}>\n {label}\n </label>\n )}\n <div\n ref={setFieldNode}\n className={cn(styles.field, disabled && styles.disabled)}\n onMouseDown={handleFieldMouseDown}\n >\n {selectedOptions.map((option) => (\n <span key={option.value} className={styles.chip}>\n {option.label}\n <button\n type=\"button\"\n className={styles.chipRemove}\n aria-label={`Remover ${option.label}`}\n disabled={disabled}\n onMouseDown={(event) => {\n event.preventDefault();\n removeAt(option.value);\n }}\n >\n ×\n </button>\n </span>\n ))}\n <input\n id={id}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={`${id}-listbox`}\n aria-autocomplete=\"list\"\n aria-activedescendant={activeOption ? optionId(activeIndex) : undefined}\n className={styles.input}\n placeholder={value.length === 0 ? placeholder : \"\"}\n disabled={disabled}\n value={query}\n onFocus={() => setOpen(true)}\n onChange={(event) => {\n setQuery(event.target.value);\n setOpen(true);\n setActiveIndex(0);\n }}\n onKeyDown={handleKeyDown}\n onBlur={onInputBlur}\n />\n </div>\n {open && (portal ? <Portal>{list}</Portal> : list)}\n {error ? (\n <span className={styles.errorText}>{error}</span>\n ) : helperText ? (\n <span className={styles.helper}>{helperText}</span>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("./Navbar.module.cjs");let n=require("react/jsx-runtime");function r({logo:r,nav:i,actions:a,sticky:o=!0,tone:s=`surface`,bordered:c=!0,className:l,...u}){return(0,n.jsxs)(`header`,{className:e.cn(t.default.navbar,t.default[s],o&&t.default.sticky,c&&t.default.bordered,l),...u,children:[r&&(0,n.jsx)(`div`,{className:t.default.logo,children:r}),i&&(0,n.jsx)(`nav`,{className:t.default.nav,children:i}),a&&(0,n.jsx)(`div`,{className:t.default.actions,children:a})]})}exports.Navbar=r;
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("./Navbar.module.cjs");let n=require("react/jsx-runtime");function r({logo:r,nav:i,actions:a,sticky:o=!0,tone:s=`surface`,bordered:c=!0,className:l,...u}){return(0,n.jsxs)(`header`,{className:e.cn(t.default.navbar,t.default[s],o&&t.default.sticky,c&&t.default.bordered,l),"data-tempest-tone":s===`inverse`?`inverse`:void 0,...u,children:[r&&(0,n.jsx)(`div`,{className:t.default.logo,children:r}),i&&(0,n.jsx)(`nav`,{className:t.default.nav,children:i}),a&&(0,n.jsx)(`div`,{className:t.default.actions,children:a})]})}exports.Navbar=r;
|
|
2
2
|
//# sourceMappingURL=Navbar.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Navbar.cjs","names":[],"sources":["../../../src/components/Navbar/Navbar.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Navbar.module.css\";\n\nexport type NavbarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface NavbarProps extends HTMLAttributes<HTMLElement> {\n /** Left slot — typically logo + brand name. */\n logo?: ReactNode;\n /** Center slot — typically nav links. */\n nav?: ReactNode;\n /** Right slot — typically user menu / actions. */\n actions?: ReactNode;\n /** Make the bar sticky at the top of the scroll container. Default `true`. */\n sticky?: boolean;\n
|
|
1
|
+
{"version":3,"file":"Navbar.cjs","names":[],"sources":["../../../src/components/Navbar/Navbar.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Navbar.module.css\";\n\n/**\n * Visual tone of a {@link Navbar}.\n *\n * - `\"surface\"` — the page's surface and text.\n * - `\"primary\"` — the brand fill (`--tempest-primary`) with\n * `--tempest-primary-foreground` ink. Only the bar's own text follows the fill:\n * components inside it keep the page's tokens. Measured in Chromium over the\n * SDK's `#0066ff`, a link button inside it reads 1:1, muted text 1.59:1 and the\n * focus ring 1.41:1. Fine for a bar that holds a logo and plain text.\n * - `\"inverse\"` — the inverse surface: every color token inverted inside the bar,\n * so a primary `Button`, a link, the focus ring and muted text are measured\n * against the fill. See {@link NavbarProps.tone}.\n * - `\"transparent\"` — no background; the text inherits.\n */\nexport type NavbarTone = \"surface\" | \"primary\" | \"inverse\" | \"transparent\";\n\nexport interface NavbarProps extends HTMLAttributes<HTMLElement> {\n /** Left slot — typically logo + brand name. */\n logo?: ReactNode;\n /** Center slot — typically nav links. */\n nav?: ReactNode;\n /** Right slot — typically user menu / actions. */\n actions?: ReactNode;\n /** Make the bar sticky at the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /**\n * Visual tone. Default `\"surface\"`.\n *\n * `\"inverse\"` writes `data-tempest-tone=\"inverse\"` and paints the bar with the\n * inverse surface's `--tempest-bg`, so the components in the slots invert with\n * it. The tokens are opt-in: `tempest-react-sdk/styles/inverse.css` for the\n * SDK's own blue, or `createTheme({ primary, inverse: createInverseSurface })`\n * for yours. With neither loaded the bar falls back to the page's\n * `--tempest-bg` and `--tempest-text` — legible, not inverted.\n *\n * A separate tone rather than a change to `\"primary\"`, because the inverse\n * fill is not always the brand's `500`: for `#0066ff` it is the `800`\n * (`#042e75`), and turning `\"primary\"` into it would repaint every bar that\n * uses it today.\n */\n tone?: NavbarTone;\n /** When set, renders a thin bottom border. Default `true`. */\n bordered?: boolean;\n}\n\n/**\n * Top app bar. Three-slot layout (logo / nav / actions) that collapses\n * gracefully on mobile (nav slot wraps below).\n *\n * @example\n * <Navbar\n * logo={<img src=\"/logo.svg\" alt=\"App\" />}\n * nav={<NavLinks />}\n * actions={<UserMenu />}\n * />\n */\nexport function Navbar({\n logo,\n nav,\n actions,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n className,\n ...props\n}: NavbarProps) {\n return (\n <header\n className={cn(\n styles.navbar,\n styles[tone],\n sticky && styles.sticky,\n bordered && styles.bordered,\n className,\n )}\n data-tempest-tone={tone === \"inverse\" ? \"inverse\" : undefined}\n {...props}\n >\n {logo && <div className={styles.logo}>{logo}</div>}\n {nav && <nav className={styles.nav}>{nav}</nav>}\n {actions && <div className={styles.actions}>{actions}</div>}\n </header>\n );\n}\n"],"mappings":"0GA4DA,SAAgB,EAAO,CACnB,OACA,MACA,UACA,SAAS,GACT,OAAO,UACP,WAAW,GACX,YACA,GAAG,GACS,CACZ,OACI,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,OACP,EAAA,QAAO,GACP,GAAU,EAAA,QAAO,OACjB,GAAY,EAAA,QAAO,SACnB,CACJ,EACA,oBAAmB,IAAS,UAAY,UAAY,IAAA,GACpD,GAAI,EATR,SAAA,CAWK,IAAQ,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,SAAA,CAAU,CAAA,EAChD,IAAO,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,IAAM,SAAA,CAAS,CAAA,EAC7C,IAAW,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAU,SAAA,CAAa,CAAA,CACtD,GAEhB"}
|
|
@@ -5,6 +5,7 @@ import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
|
5
5
|
function i({ logo: i, nav: a, actions: o, sticky: s = !0, tone: c = "surface", bordered: l = !0, className: u, ...d }) {
|
|
6
6
|
return /* @__PURE__ */ r("header", {
|
|
7
7
|
className: e(t.navbar, t[c], s && t.sticky, l && t.bordered, u),
|
|
8
|
+
"data-tempest-tone": c === "inverse" ? "inverse" : void 0,
|
|
8
9
|
...d,
|
|
9
10
|
children: [
|
|
10
11
|
i && /* @__PURE__ */ n("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Navbar.js","names":[],"sources":["../../../src/components/Navbar/Navbar.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Navbar.module.css\";\n\nexport type NavbarTone = \"surface\" | \"primary\" | \"transparent\";\n\nexport interface NavbarProps extends HTMLAttributes<HTMLElement> {\n /** Left slot — typically logo + brand name. */\n logo?: ReactNode;\n /** Center slot — typically nav links. */\n nav?: ReactNode;\n /** Right slot — typically user menu / actions. */\n actions?: ReactNode;\n /** Make the bar sticky at the top of the scroll container. Default `true`. */\n sticky?: boolean;\n
|
|
1
|
+
{"version":3,"file":"Navbar.js","names":[],"sources":["../../../src/components/Navbar/Navbar.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Navbar.module.css\";\n\n/**\n * Visual tone of a {@link Navbar}.\n *\n * - `\"surface\"` — the page's surface and text.\n * - `\"primary\"` — the brand fill (`--tempest-primary`) with\n * `--tempest-primary-foreground` ink. Only the bar's own text follows the fill:\n * components inside it keep the page's tokens. Measured in Chromium over the\n * SDK's `#0066ff`, a link button inside it reads 1:1, muted text 1.59:1 and the\n * focus ring 1.41:1. Fine for a bar that holds a logo and plain text.\n * - `\"inverse\"` — the inverse surface: every color token inverted inside the bar,\n * so a primary `Button`, a link, the focus ring and muted text are measured\n * against the fill. See {@link NavbarProps.tone}.\n * - `\"transparent\"` — no background; the text inherits.\n */\nexport type NavbarTone = \"surface\" | \"primary\" | \"inverse\" | \"transparent\";\n\nexport interface NavbarProps extends HTMLAttributes<HTMLElement> {\n /** Left slot — typically logo + brand name. */\n logo?: ReactNode;\n /** Center slot — typically nav links. */\n nav?: ReactNode;\n /** Right slot — typically user menu / actions. */\n actions?: ReactNode;\n /** Make the bar sticky at the top of the scroll container. Default `true`. */\n sticky?: boolean;\n /**\n * Visual tone. Default `\"surface\"`.\n *\n * `\"inverse\"` writes `data-tempest-tone=\"inverse\"` and paints the bar with the\n * inverse surface's `--tempest-bg`, so the components in the slots invert with\n * it. The tokens are opt-in: `tempest-react-sdk/styles/inverse.css` for the\n * SDK's own blue, or `createTheme({ primary, inverse: createInverseSurface })`\n * for yours. With neither loaded the bar falls back to the page's\n * `--tempest-bg` and `--tempest-text` — legible, not inverted.\n *\n * A separate tone rather than a change to `\"primary\"`, because the inverse\n * fill is not always the brand's `500`: for `#0066ff` it is the `800`\n * (`#042e75`), and turning `\"primary\"` into it would repaint every bar that\n * uses it today.\n */\n tone?: NavbarTone;\n /** When set, renders a thin bottom border. Default `true`. */\n bordered?: boolean;\n}\n\n/**\n * Top app bar. Three-slot layout (logo / nav / actions) that collapses\n * gracefully on mobile (nav slot wraps below).\n *\n * @example\n * <Navbar\n * logo={<img src=\"/logo.svg\" alt=\"App\" />}\n * nav={<NavLinks />}\n * actions={<UserMenu />}\n * />\n */\nexport function Navbar({\n logo,\n nav,\n actions,\n sticky = true,\n tone = \"surface\",\n bordered = true,\n className,\n ...props\n}: NavbarProps) {\n return (\n <header\n className={cn(\n styles.navbar,\n styles[tone],\n sticky && styles.sticky,\n bordered && styles.bordered,\n className,\n )}\n data-tempest-tone={tone === \"inverse\" ? \"inverse\" : undefined}\n {...props}\n >\n {logo && <div className={styles.logo}>{logo}</div>}\n {nav && <nav className={styles.nav}>{nav}</nav>}\n {actions && <div className={styles.actions}>{actions}</div>}\n </header>\n );\n}\n"],"mappings":";;;;AA4DA,SAAgB,EAAO,EACnB,SACA,QACA,YACA,YAAS,IACT,UAAO,WACP,cAAW,IACX,cACA,GAAG,KACS;CACZ,OACI,kBAAC,UAAD;EACI,WAAW,EACP,EAAO,QACP,EAAO,IACP,KAAU,EAAO,QACjB,KAAY,EAAO,UACnB,CACJ;EACA,qBAAmB,MAAS,YAAY,YAAY,KAAA;EACpD,GAAI;EATR,UAAA;GAWK,KAAQ,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO,UAAA;GAAU,CAAA;GAChD,KAAO,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAM,UAAA;GAAS,CAAA;GAC7C,KAAW,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAU,UAAA;GAAa,CAAA;EACtD;;AAEhB"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
require("../../styles/component/Navbar.css");
|
|
2
|
-
var e=`tempest_navbar_Ey25T`,t=`tempest_surface_xyKhM`,n=`tempest_primary_w3ft-`,r=`
|
|
2
|
+
var e=`tempest_navbar_Ey25T`,t=`tempest_surface_xyKhM`,n=`tempest_primary_w3ft-`,r=`tempest_inverse_f26-i`,i=`tempest_transparent_N1vMR`,a=`tempest_bordered_HD49R`,o=`tempest_sticky_0XsOs`,s=`tempest_logo_O9j3q`,c=`tempest_nav_0oisE`,l=`tempest_actions_8BNWJ`,u={navbar:e,surface:t,primary:n,inverse:r,transparent:i,bordered:a,sticky:o,logo:s,nav:c,actions:l};exports.actions=l,exports.bordered=a,exports.default=u,exports.inverse=r,exports.logo=s,exports.nav=c,exports.navbar=e,exports.primary=n,exports.sticky=o,exports.surface=t,exports.transparent=i;
|
|
3
3
|
//# sourceMappingURL=Navbar.module.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Navbar.module.cjs","names":[],"sources":["../../../src/components/Navbar/Navbar.module.css"],"sourcesContent":[".navbar {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-4);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-height: 56px;\n width: 100%;\n font-family: var(--tempest-font-sans);\n padding-top: max(var(--tempest-space-3), env(safe-area-inset-top, 0));\n}\n\n.navbar.surface {\n background: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.navbar.primary {\n background: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.navbar.transparent {\n background: transparent;\n color: inherit;\n}\n\n.navbar.bordered {\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.navbar.sticky {\n position: sticky;\n top: 0;\n z-index: 30;\n backdrop-filter: saturate(180%) blur(8px);\n}\n\n.logo {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n.nav {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 0 0 auto;\n margin-left: auto;\n}\n"],"mappings":";"}
|
|
1
|
+
{"version":3,"file":"Navbar.module.cjs","names":[],"sources":["../../../src/components/Navbar/Navbar.module.css"],"sourcesContent":[".navbar {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-4);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-height: 56px;\n width: 100%;\n font-family: var(--tempest-font-sans);\n padding-top: max(var(--tempest-space-3), env(safe-area-inset-top, 0));\n}\n\n.navbar.surface {\n background: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.navbar.primary {\n background: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.navbar.inverse {\n background: var(--tempest-bg);\n color: var(--tempest-text);\n}\n\n.navbar.transparent {\n background: transparent;\n color: inherit;\n}\n\n.navbar.bordered {\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.navbar.sticky {\n position: sticky;\n top: 0;\n z-index: 30;\n backdrop-filter: saturate(180%) blur(8px);\n}\n\n.logo {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n.nav {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 0 0 auto;\n margin-left: auto;\n}\n"],"mappings":";"}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import "../../styles/component/Navbar.css";
|
|
2
2
|
//#region src/components/Navbar/Navbar.module.css
|
|
3
|
-
var e = "tempest_navbar_Ey25T", t = "tempest_surface_xyKhM", n = "tempest_primary_w3ft-", r = "
|
|
3
|
+
var e = "tempest_navbar_Ey25T", t = "tempest_surface_xyKhM", n = "tempest_primary_w3ft-", r = "tempest_inverse_f26-i", i = "tempest_transparent_N1vMR", a = "tempest_bordered_HD49R", o = "tempest_sticky_0XsOs", s = "tempest_logo_O9j3q", c = "tempest_nav_0oisE", l = "tempest_actions_8BNWJ", u = {
|
|
4
4
|
navbar: e,
|
|
5
5
|
surface: t,
|
|
6
6
|
primary: n,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
inverse: r,
|
|
8
|
+
transparent: i,
|
|
9
|
+
bordered: a,
|
|
10
|
+
sticky: o,
|
|
11
|
+
logo: s,
|
|
12
|
+
nav: c,
|
|
13
|
+
actions: l
|
|
13
14
|
};
|
|
14
15
|
//#endregion
|
|
15
|
-
export {
|
|
16
|
+
export { l as actions, a as bordered, u as default, r as inverse, s as logo, c as nav, e as navbar, n as primary, o as sticky, t as surface, i as transparent };
|
|
16
17
|
|
|
17
18
|
//# sourceMappingURL=Navbar.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Navbar.module.js","names":[],"sources":["../../../src/components/Navbar/Navbar.module.css"],"sourcesContent":[".navbar {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-4);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-height: 56px;\n width: 100%;\n font-family: var(--tempest-font-sans);\n padding-top: max(var(--tempest-space-3), env(safe-area-inset-top, 0));\n}\n\n.navbar.surface {\n background: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.navbar.primary {\n background: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.navbar.transparent {\n background: transparent;\n color: inherit;\n}\n\n.navbar.bordered {\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.navbar.sticky {\n position: sticky;\n top: 0;\n z-index: 30;\n backdrop-filter: saturate(180%) blur(8px);\n}\n\n.logo {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n.nav {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 0 0 auto;\n margin-left: auto;\n}\n"],"mappings":";"}
|
|
1
|
+
{"version":3,"file":"Navbar.module.js","names":[],"sources":["../../../src/components/Navbar/Navbar.module.css"],"sourcesContent":[".navbar {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-4);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-height: 56px;\n width: 100%;\n font-family: var(--tempest-font-sans);\n padding-top: max(var(--tempest-space-3), env(safe-area-inset-top, 0));\n}\n\n.navbar.surface {\n background: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.navbar.primary {\n background: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.navbar.inverse {\n background: var(--tempest-bg);\n color: var(--tempest-text);\n}\n\n.navbar.transparent {\n background: transparent;\n color: inherit;\n}\n\n.navbar.bordered {\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.navbar.sticky {\n position: sticky;\n top: 0;\n z-index: 30;\n backdrop-filter: saturate(180%) blur(8px);\n}\n\n.logo {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n.nav {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n flex: 0 0 auto;\n margin-left: auto;\n}\n"],"mappings":";"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationMenu.module.cjs","names":[],"sources":["../../../src/components/NavigationMenu/NavigationMenu.module.css"],"sourcesContent":[".root {\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n}\n\n.list {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-1);\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.item {\n position: relative;\n}\n\n.link,\n.trigger {\n display: inline-flex;\n align-items: center;\n gap: var(--tempest-space-1);\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n text-decoration: none;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .link:hover,\n .trigger:hover {\n background-color: var(--tempest-surface);\n }\n}\n\n.link:focus-visible,\n.trigger:focus-visible {\n outline:
|
|
1
|
+
{"version":3,"file":"NavigationMenu.module.cjs","names":[],"sources":["../../../src/components/NavigationMenu/NavigationMenu.module.css"],"sourcesContent":[".root {\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n}\n\n.list {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-1);\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.item {\n position: relative;\n}\n\n.link,\n.trigger {\n display: inline-flex;\n align-items: center;\n gap: var(--tempest-space-1);\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n text-decoration: none;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .link:hover,\n .trigger:hover {\n background-color: var(--tempest-surface);\n }\n}\n\n.link:focus-visible,\n.trigger:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n.panel {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: var(--tempest-z-dropdown);\n min-width: 180px;\n max-width: 320px;\n background-color: var(--tempest-bg);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-1);\n list-style: none;\n margin: 0;\n animation: tempest-nav-menu-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.subItem {\n display: block;\n}\n\n.subItem .link {\n width: 100%;\n}\n\n@keyframes tempest-nav-menu-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel {\n animation: none;\n }\n}\n\n.portalled {\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n z-index: var(--tempest-z-popover);\n}\n"],"mappings":";"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationMenu.module.js","names":[],"sources":["../../../src/components/NavigationMenu/NavigationMenu.module.css"],"sourcesContent":[".root {\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n}\n\n.list {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-1);\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.item {\n position: relative;\n}\n\n.link,\n.trigger {\n display: inline-flex;\n align-items: center;\n gap: var(--tempest-space-1);\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n text-decoration: none;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .link:hover,\n .trigger:hover {\n background-color: var(--tempest-surface);\n }\n}\n\n.link:focus-visible,\n.trigger:focus-visible {\n outline:
|
|
1
|
+
{"version":3,"file":"NavigationMenu.module.js","names":[],"sources":["../../../src/components/NavigationMenu/NavigationMenu.module.css"],"sourcesContent":[".root {\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n}\n\n.list {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-1);\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.item {\n position: relative;\n}\n\n.link,\n.trigger {\n display: inline-flex;\n align-items: center;\n gap: var(--tempest-space-1);\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n text-decoration: none;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .link:hover,\n .trigger:hover {\n background-color: var(--tempest-surface);\n }\n}\n\n.link:focus-visible,\n.trigger:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n.panel {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: var(--tempest-z-dropdown);\n min-width: 180px;\n max-width: 320px;\n background-color: var(--tempest-bg);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-1);\n list-style: none;\n margin: 0;\n animation: tempest-nav-menu-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.subItem {\n display: block;\n}\n\n.subItem .link {\n width: 100%;\n}\n\n@keyframes tempest-nav-menu-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel {\n animation: none;\n }\n}\n\n.portalled {\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n z-index: var(--tempest-z-popover);\n}\n"],"mappings":";"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationRail.module.cjs","names":[],"sources":["../../../src/components/NavigationRail/NavigationRail.module.css"],"sourcesContent":[".rail {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 80px;\n height: 100%;\n padding: var(--tempest-space-3) var(--tempest-space-2);\n background: var(--tempest-surface);\n border-right: 1px solid var(--tempest-border);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-bottom: var(--tempest-space-2);\n}\n\n.list {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--tempest-space-1);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-top: auto;\n padding-top: var(--tempest-space-2);\n}\n\n.item {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-1);\n background: none;\n border: 0;\n border-radius: var(--tempest-radius-md);\n cursor: pointer;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-2xs);\n transition:\n color var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out),\n background var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out);\n}\n\n.item:hover {\n color: var(--tempest-text);\n background: var(--tempest-surface-2);\n}\n\n.item:focus-visible {\n outline: var(--tempest-focus-ring-width, 2px) solid\n var(--tempest-focus-ring-color,
|
|
1
|
+
{"version":3,"file":"NavigationRail.module.cjs","names":[],"sources":["../../../src/components/NavigationRail/NavigationRail.module.css"],"sourcesContent":[".rail {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 80px;\n height: 100%;\n padding: var(--tempest-space-3) var(--tempest-space-2);\n background: var(--tempest-surface);\n border-right: 1px solid var(--tempest-border);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-bottom: var(--tempest-space-2);\n}\n\n.list {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--tempest-space-1);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-top: auto;\n padding-top: var(--tempest-space-2);\n}\n\n.item {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-1);\n background: none;\n border: 0;\n border-radius: var(--tempest-radius-md);\n cursor: pointer;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-2xs);\n transition:\n color var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out),\n background var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out);\n}\n\n.item:hover {\n color: var(--tempest-text);\n background: var(--tempest-surface-2);\n}\n\n.item:focus-visible {\n outline: var(--tempest-focus-ring-width, 2px) solid\n var(--tempest-focus-ring-color, currentColor);\n outline-offset: var(--tempest-focus-ring-offset, 2px);\n}\n\n.item.active {\n color: var(--tempest-primary-on-soft);\n background: var(--tempest-primary-soft);\n}\n\n.item:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.iconWrap {\n position: relative;\n display: inline-flex;\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n font-size: 24px;\n}\n\n.badge {\n position: absolute;\n top: -4px;\n right: -8px;\n min-width: 14px;\n height: 14px;\n padding: 0 4px;\n border-radius: var(--tempest-radius-full);\n background: var(--tempest-danger-solid);\n color: var(--tempest-danger-on-solid);\n font-size: 10px;\n line-height: 14px;\n text-align: center;\n font-weight: var(--tempest-weight-semibold);\n}\n\n.label {\n line-height: 1;\n text-align: center;\n}\n"],"mappings":";"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationRail.module.js","names":[],"sources":["../../../src/components/NavigationRail/NavigationRail.module.css"],"sourcesContent":[".rail {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 80px;\n height: 100%;\n padding: var(--tempest-space-3) var(--tempest-space-2);\n background: var(--tempest-surface);\n border-right: 1px solid var(--tempest-border);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-bottom: var(--tempest-space-2);\n}\n\n.list {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--tempest-space-1);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-top: auto;\n padding-top: var(--tempest-space-2);\n}\n\n.item {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-1);\n background: none;\n border: 0;\n border-radius: var(--tempest-radius-md);\n cursor: pointer;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-2xs);\n transition:\n color var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out),\n background var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out);\n}\n\n.item:hover {\n color: var(--tempest-text);\n background: var(--tempest-surface-2);\n}\n\n.item:focus-visible {\n outline: var(--tempest-focus-ring-width, 2px) solid\n var(--tempest-focus-ring-color,
|
|
1
|
+
{"version":3,"file":"NavigationRail.module.js","names":[],"sources":["../../../src/components/NavigationRail/NavigationRail.module.css"],"sourcesContent":[".rail {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 80px;\n height: 100%;\n padding: var(--tempest-space-3) var(--tempest-space-2);\n background: var(--tempest-surface);\n border-right: 1px solid var(--tempest-border);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-bottom: var(--tempest-space-2);\n}\n\n.list {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--tempest-space-1);\n width: 100%;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-top: auto;\n padding-top: var(--tempest-space-2);\n}\n\n.item {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-1);\n background: none;\n border: 0;\n border-radius: var(--tempest-radius-md);\n cursor: pointer;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-2xs);\n transition:\n color var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out),\n background var(--tempest-duration-fast, 120ms) var(--tempest-ease-out, ease-out);\n}\n\n.item:hover {\n color: var(--tempest-text);\n background: var(--tempest-surface-2);\n}\n\n.item:focus-visible {\n outline: var(--tempest-focus-ring-width, 2px) solid\n var(--tempest-focus-ring-color, currentColor);\n outline-offset: var(--tempest-focus-ring-offset, 2px);\n}\n\n.item.active {\n color: var(--tempest-primary-on-soft);\n background: var(--tempest-primary-soft);\n}\n\n.item:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.iconWrap {\n position: relative;\n display: inline-flex;\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n font-size: 24px;\n}\n\n.badge {\n position: absolute;\n top: -4px;\n right: -8px;\n min-width: 14px;\n height: 14px;\n padding: 0 4px;\n border-radius: var(--tempest-radius-full);\n background: var(--tempest-danger-solid);\n color: var(--tempest-danger-on-solid);\n font-size: 10px;\n line-height: 14px;\n text-align: center;\n font-weight: var(--tempest-weight-semibold);\n}\n\n.label {\n line-height: 1;\n text-align: center;\n}\n"],"mappings":";"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationCenter.module.cjs","names":[],"sources":["../../../src/components/NotificationCenter/NotificationCenter.module.css"],"sourcesContent":[".panel {\n display: flex;\n flex-direction: column;\n min-width: 0;\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.title {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n margin: 0;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n}\n\n.headerAction {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-primary);\n font: inherit;\n font-size: var(--tempest-text-xs);\n cursor: pointer;\n white-space: nowrap;\n}\n\n.headerAction:hover {\n text-decoration: underline;\n}\n\n.headerAction:focus-visible {\n outline:
|
|
1
|
+
{"version":3,"file":"NotificationCenter.module.cjs","names":[],"sources":["../../../src/components/NotificationCenter/NotificationCenter.module.css"],"sourcesContent":[".panel {\n display: flex;\n flex-direction: column;\n min-width: 0;\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.title {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n margin: 0;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n}\n\n.headerAction {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-primary);\n font: inherit;\n font-size: var(--tempest-text-xs);\n cursor: pointer;\n white-space: nowrap;\n}\n\n.headerAction:hover {\n text-decoration: underline;\n}\n\n.headerAction:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n border-radius: var(--tempest-radius-sm);\n}\n\n.list {\n display: flex;\n flex-direction: column;\n margin: 0;\n padding: 0;\n list-style: none;\n overflow-y: auto;\n}\n\n.item {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n border-bottom: 1px solid var(--tempest-border);\n}\n\n.item:last-child {\n border-bottom: none;\n}\n\n/*\n * Unread is marked by a left bar plus a tinted background, never by colour alone\n * — the distinction has to survive a monochrome display and colour blindness.\n * `aria-current` on the row carries the same information non-visually.\n */\n.item.unread {\n background-color: var(--tempest-surface);\n box-shadow: inset 3px 0 0 0 var(--tempest-primary);\n}\n\n.icon {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 2rem;\n height: 2rem;\n border-radius: var(--tempest-radius-full);\n background-color: var(--tempest-surface-2, var(--tempest-surface));\n color: var(--tempest-text-muted);\n}\n\n.body {\n display: flex;\n flex: 1;\n min-width: 0;\n flex-direction: column;\n gap: var(--tempest-space-1);\n}\n\n/*\n * The whole title/body block is one button so the hit target matches what the\n * user perceives as \"the notification\", instead of just its title text.\n */\n.trigger {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.trigger:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n border-radius: var(--tempest-radius-sm);\n}\n\n.itemTitle {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n}\n\n.itemBody {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n}\n\n.time {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n}\n\n.dismiss {\n flex-shrink: 0;\n width: 1.5rem;\n height: 1.5rem;\n padding: 0;\n border: none;\n border-radius: var(--tempest-radius-full);\n background: none;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-base);\n line-height: 1;\n cursor: pointer;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .dismiss:hover {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n }\n}\n\n.dismiss:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: var(--tempest-focus-ring-offset);\n}\n"],"mappings":";"}
|