@krizaka/ui 2.10.0 → 3.0.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 +3 -1
- package/dist/audio-player.js +1 -1
- package/dist/{chunk-EUPTXKH7.js → chunk-BRKMQ6JV.js} +2 -1
- package/dist/chunk-FDCMBWUU.js +168 -0
- package/dist/chunk-FDCMBWUU.js.map +1 -0
- package/dist/chunk-HCNOOPZE.js +210 -0
- package/dist/chunk-HCNOOPZE.js.map +1 -0
- package/dist/{chunk-X6TW3X6Y.js → chunk-LCZXS6AK.js} +2 -2
- package/dist/chunk-OGJI5X4W.js +157 -0
- package/dist/chunk-OGJI5X4W.js.map +1 -0
- package/dist/combobox.js +2 -2
- package/dist/data-table.d.ts +284 -24
- package/dist/data-table.js +914 -121
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.js +1 -1
- package/dist/filter-bar.d.ts +12 -12
- package/dist/filter-bar.js +9 -145
- package/dist/filter-bar.js.map +1 -1
- package/dist/image-cropper.js +1 -1
- package/dist/media-capture.js +1 -1
- package/dist/native/list.d.ts +95 -0
- package/dist/native/list.js +135 -0
- package/dist/native/list.js.map +1 -0
- package/dist/search-field.js +6 -160
- package/dist/search-field.js.map +1 -1
- package/dist/tag-input.js +2 -2
- package/dist/video-player.js +1 -1
- package/dist/video-trimmer.js +1 -1
- package/dist/virtual.d.ts +140 -0
- package/dist/virtual.js +19 -0
- package/dist/virtual.js.map +1 -0
- package/package.json +66 -6
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/app-shell.json +1 -1
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +1 -1
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/button.json +1 -1
- package/registry/card-grid.json +1 -1
- package/registry/card.json +1 -1
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/chip-link.json +1 -1
- package/registry/chip.json +1 -1
- package/registry/choice-grid.json +1 -1
- package/registry/code-block.json +1 -1
- package/registry/combobox.json +1 -1
- package/registry/command.json +1 -1
- package/registry/component-page.json +1 -1
- package/registry/confirm-button.json +1 -1
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +1 -1
- package/registry/countdown.json +1 -1
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +643 -49
- package/registry/date-picker.json +1 -1
- package/registry/decision-list.json +1 -1
- package/registry/detail-layout.json +1 -1
- package/registry/dialog.json +1 -1
- package/registry/docs-article-page.json +1 -1
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/empty-state.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/data-table/details.d.ts +4 -0
- package/registry/examples/data-table/details.js +33 -0
- package/registry/examples/data-table/details.tsx +31 -0
- package/registry/examples/data-table/filters.d.ts +4 -0
- package/registry/examples/data-table/filters.js +40 -0
- package/registry/examples/data-table/filters.tsx +40 -0
- package/registry/examples/data-table/grouped.d.ts +4 -0
- package/registry/examples/data-table/grouped.js +24 -0
- package/registry/examples/data-table/grouped.tsx +23 -0
- package/registry/examples/data-table/paginated.d.ts +4 -0
- package/registry/examples/data-table/paginated.js +23 -0
- package/registry/examples/data-table/paginated.tsx +22 -0
- package/registry/examples/data-table/pinned.d.ts +4 -0
- package/registry/examples/data-table/pinned.js +26 -0
- package/registry/examples/data-table/pinned.tsx +25 -0
- package/registry/examples/data-table/server.d.ts +4 -0
- package/registry/examples/data-table/server.js +40 -0
- package/registry/examples/data-table/server.tsx +40 -0
- package/registry/examples/data-table/virtualized.js +2 -2
- package/registry/examples/data-table/virtualized.tsx +3 -3
- package/registry/examples/list/native/default.d.ts +4 -0
- package/registry/examples/list/native/default.dark.png +0 -0
- package/registry/examples/list/native/default.js +33 -0
- package/registry/examples/list/native/default.light.png +0 -0
- package/registry/examples/list/native/default.tsx +31 -0
- package/registry/examples/list/native/infinite.d.ts +4 -0
- package/registry/examples/list/native/infinite.dark.png +0 -0
- package/registry/examples/list/native/infinite.js +30 -0
- package/registry/examples/list/native/infinite.light.png +0 -0
- package/registry/examples/list/native/infinite.tsx +35 -0
- package/registry/examples/list/native/states.d.ts +4 -0
- package/registry/examples/list/native/states.dark.png +0 -0
- package/registry/examples/list/native/states.js +26 -0
- package/registry/examples/list/native/states.light.png +0 -0
- package/registry/examples/list/native/states.tsx +21 -0
- package/registry/examples/virtual/feed.d.ts +4 -0
- package/registry/examples/virtual/feed.js +26 -0
- package/registry/examples/virtual/feed.tsx +24 -0
- package/registry/examples/virtual/gallery.d.ts +4 -0
- package/registry/examples/virtual/gallery.js +31 -0
- package/registry/examples/virtual/gallery.tsx +28 -0
- package/registry/examples/virtual/messages.d.ts +4 -0
- package/registry/examples/virtual/messages.js +28 -0
- package/registry/examples/virtual/messages.tsx +28 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +1 -1
- package/registry/filter-bar.json +1 -1
- package/registry/gallery-page.json +1 -1
- package/registry/glow-card.json +1 -1
- package/registry/goal-gauge.json +1 -1
- package/registry/how-it-works-page.json +1 -1
- package/registry/image-cropper.json +1 -1
- package/registry/index.json +48 -5
- package/registry/iso.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +1 -1
- package/registry/landscape.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +1 -1
- package/registry/list.json +249 -0
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +1 -1
- package/registry/mascots.json +1 -1
- package/registry/media-capture.json +1 -1
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +1 -1
- package/registry/number-ticker.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +1 -1
- package/registry/page.json +1 -1
- package/registry/pagination.json +1 -1
- package/registry/pointer-light.json +1 -1
- package/registry/popover.json +1 -1
- package/registry/pricing-page.json +1 -1
- package/registry/product-page.json +1 -1
- package/registry/profile-card.json +1 -1
- package/registry/progress.json +1 -1
- package/registry/promise-card.json +1 -1
- package/registry/radio-group.json +1 -1
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +1 -1
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/settings-layout.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/slider.json +1 -1
- package/registry/slot.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +1 -1
- package/registry/stat.json +1 -1
- package/registry/status-page.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/theme.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +1 -1
- package/registry/video-player.json +1 -1
- package/registry/video-trimmer.json +1 -1
- package/registry/virtual.json +569 -0
- package/skills/data-table/SKILL.md +56 -0
- package/skills/native-list/SKILL.md +39 -0
- package/skills/virtual/SKILL.md +35 -0
- /package/dist/{chunk-EUPTXKH7.js.map → chunk-BRKMQ6JV.js.map} +0 -0
- /package/dist/{chunk-X6TW3X6Y.js.map → chunk-LCZXS6AK.js.map} +0 -0
package/dist/filter-bar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/filter-bar/filter-bar.tsx","../src/dropdown-menu/dropdown-menu.tsx"],"sourcesContent":["// Client (menus in a portal). The bar above a list: the controls that narrow it (a SearchField, FilterMenus, a\n// DateRangePicker — any child), the filters in force as removable chips, \"Clear all\", and the count of results in a\n// polite live region. It holds no state: the app keeps the filters — in the address, ideally — and the\n// `readFilterParams` / `writeFilterParams` helpers move them between a `URLSearchParams` and an object.\nimport { DropdownMenu as MenuPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { Chip } from \"../chip\";\nimport { DropdownMenu, menu } from \"../dropdown-menu\";\nimport { ChevronGlyph } from \"../glyphs\";\n\nexport const filterBar = tv({\n slots: {\n root: \"flex w-full flex-col gap-3\",\n controls: \"flex w-full flex-wrap items-center gap-2 [&>*]:min-w-0\",\n active: \"flex flex-wrap items-center gap-2\",\n clear:\n \"rounded-md px-2 py-1 text-xs font-semibold text-fg-accent hover:bg-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n results: \"ml-auto text-xs text-fg-secondary tabular-nums\",\n trigger:\n \"inline-flex h-11 shrink-0 items-center gap-2 rounded-lg border border-border-default bg-surface-1 px-3.5 text-sm font-semibold text-fg-secondary \" +\n \"transition-colors hover:border-border-strong hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n \"data-[active]:border-accent data-[active]:bg-accent-soft data-[active]:text-fg data-[state=open]:border-border-strong\",\n count: \"inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-accent px-1.5 text-[11px] font-bold tabular-nums text-on-accent\",\n optionCount: \"ml-auto pl-4 text-xs tabular-nums text-fg-secondary\",\n },\n});\n\nexport type ActiveFilter = {\n /** A stable key (`\"status:failed\"`). */\n id: string;\n /** What the chip says, passed translated (\"Status: Failed\"). */\n label: string;\n /** Removes this filter. */\n onRemove: () => void;\n};\n\nexport type FilterBarProps = Omit<React.ComponentProps<\"div\">, \"children\" | \"results\"> & {\n /** The accessible name of the bar (\"Filter jobs\"), passed translated. */\n label: string;\n /** The controls: `SearchField`, `FilterMenu`, `DateRangePicker`, a `Chip.Group`… */\n children?: React.ReactNode;\n /** The filters in force, each a removable chip. */\n active?: readonly ActiveFilter[];\n /** Clears every filter; the \"Clear all\" button shows when there is at least one. */\n onClearAll?: () => void;\n /** The \"Clear all\" button (\"Clear filters\"), passed translated. */\n clearLabel?: string;\n /** A chip's remove button: `(label) => \"Remove \" + label`, passed translated. */\n removeLabel?: (label: string) => string;\n /** The count of results (\"42 jobs\"), announced politely when it changes. */\n results?: string;\n};\n\n/** The bar of filters above a list: controls, active filters as chips, clear all, the count of results. */\nexport function FilterBar({\n label,\n children,\n active = [],\n onClearAll,\n clearLabel = \"Clear filters\",\n removeLabel = (text) => `Remove ${text}`,\n results,\n className,\n ...props\n}: FilterBarProps) {\n const s = filterBar();\n return (\n <div role=\"group\" aria-label={label} className={s.root({ className })} {...props}>\n {children ? <div className={s.controls()}>{children}</div> : null}\n {active.length > 0 || results !== undefined ? (\n <div className={s.active()}>\n {active.map((filter) => (\n <Chip key={filter.id} size=\"sm\" removable removeLabel={removeLabel(filter.label)} onRemove={filter.onRemove}>\n {filter.label}\n </Chip>\n ))}\n {active.length > 0 && onClearAll ? (\n <button type=\"button\" className={s.clear()} onClick={onClearAll}>\n {clearLabel}\n </button>\n ) : null}\n <p className={s.results()} aria-live=\"polite\" aria-atomic>\n {results}\n </p>\n </div>\n ) : null}\n </div>\n );\n}\n\nexport type FilterOption = {\n /** What the app stores (and puts in the address). */\n value: string;\n /** What the person reads, passed translated. */\n label: string;\n /** How many results it would give: shown on the right. */\n count?: number;\n /** Shown but not choosable. */\n disabled?: boolean;\n};\n\ntype FilterMenuBase = {\n /** The name of the filter on its button (\"Status\"), passed translated. */\n label: string;\n /** The choices. */\n options: readonly FilterOption[];\n /** Opens the menu at first (a story, a test). */\n defaultOpen?: boolean;\n /** Classes merged on the button. */\n className?: string;\n};\n\nexport type FilterMenuProps =\n | (FilterMenuBase & {\n /** Several values (checkboxes, default) or one (radios). */\n multiple?: true;\n /** The values chosen. */\n value: readonly string[];\n /** Called with the values chosen. */\n onValueChange: (value: string[]) => void;\n })\n | (FilterMenuBase & {\n /** One value: the options are radios. */\n multiple: false;\n /** The value chosen, `\"\"` for none. */\n value: string;\n /** Called with the value chosen. */\n onValueChange: (value: string) => void;\n });\n\n/** A filter's button and its menu of options — checkboxes (several values) or radios (one) — with a count of chosen ones. */\nexport function FilterMenu(props: FilterMenuProps) {\n const { label, options, defaultOpen, className } = props;\n const s = filterBar();\n const m = menu();\n const chosen = props.multiple === false ? (props.value ? [props.value] : []) : props.value;\n const single = props.multiple === false ? options.find((o) => o.value === props.value) : undefined;\n const content = (option: FilterOption) => (\n <>\n {option.label}\n {option.count !== undefined ? <span className={s.optionCount()}>{option.count}</span> : null}\n </>\n );\n return (\n <DropdownMenu.Root defaultOpen={defaultOpen} modal={false}>\n <DropdownMenu.Trigger className={s.trigger({ className })} data-active={chosen.length > 0 ? \"\" : undefined}>\n {single ? `${label}: ${single.label}` : label}\n {props.multiple !== false && chosen.length > 0 ? <span className={s.count()}>{chosen.length}</span> : null}\n <ChevronGlyph className=\"h-4 w-4\" />\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"start\" aria-label={label}>\n {props.multiple === false ? (\n <MenuPrimitive.RadioGroup value={props.value} onValueChange={props.onValueChange}>\n {options.map((option) => (\n <MenuPrimitive.RadioItem key={option.value} value={option.value} disabled={option.disabled} className={m.item()}>\n <span className=\"flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-border-strong\">\n <MenuPrimitive.ItemIndicator className=\"h-2 w-2 rounded-full bg-accent\" />\n </span>\n {content(option)}\n </MenuPrimitive.RadioItem>\n ))}\n </MenuPrimitive.RadioGroup>\n ) : (\n options.map((option) => (\n <DropdownMenu.CheckboxItem\n key={option.value}\n disabled={option.disabled}\n checked={props.value.includes(option.value)}\n onSelect={(event) => event.preventDefault()}\n onCheckedChange={(checked) =>\n props.onValueChange(checked ? [...props.value, option.value] : props.value.filter((v) => v !== option.value))\n }\n >\n {content(option)}\n </DropdownMenu.CheckboxItem>\n ))\n )}\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n}\n\n/** Filters read from an address: `{ status: [\"failed\", \"running\"], q: [\"cat\"] }` for the keys asked (repeated or comma-separated values). */\nexport function readFilterParams(params: URLSearchParams, keys: readonly string[]): Record<string, string[]> {\n return Object.fromEntries(\n keys.map((key) => [key, params.getAll(key).flatMap((v) => v.split(\",\")).map((v) => v.trim()).filter(Boolean)]),\n );\n}\n\n/** Filters written into an address (a copy of `base`): empty filters removed, several values comma-separated, the page reset. */\nexport function writeFilterParams(base: URLSearchParams, filters: Record<string, string | readonly string[] | null | undefined>, resetKey = \"page\") {\n const next = new URLSearchParams(base);\n for (const [key, value] of Object.entries(filters)) {\n const list = value == null ? [] : typeof value === \"string\" ? [value] : [...value];\n if (list.filter(Boolean).length === 0) next.delete(key);\n else next.set(key, list.filter(Boolean).join(\",\"));\n }\n if (resetKey) next.delete(resetKey);\n return next;\n}\n","// Client (a portal). Radix does the roving focus, the arrows, Home/End, typeahead, Escape and the focus return.\nimport { DropdownMenu as MenuPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\nexport const menu = tv({\n slots: {\n content: cn(floating, \"min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto p-1 origin-(--radix-dropdown-menu-content-transform-origin)\"),\n item:\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-2 text-sm text-fg outline-hidden transition-colors \" +\n \"data-highlighted:bg-surface-3 data-disabled:pointer-events-none data-disabled:opacity-40 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 \" +\n \"[&>svg]:text-fg-secondary\",\n indicator: \"ml-auto flex h-4 w-4 items-center justify-center text-fg-accent\",\n label: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n separator: \"-mx-1 my-1 h-px bg-border-subtle\",\n },\n variants: {\n // The label reads in the danger text role (≥ 4.5:1 on the menu surface, both themes); the icon and the highlight\n // carry the danger fill.\n tone: { default: {}, danger: { item: \"text-fg-danger data-highlighted:bg-danger/15 [&>svg]:text-danger\" } },\n },\n defaultVariants: { tone: \"default\" },\n});\nconst s = menu();\n\nexport type DropdownMenuContentProps = React.ComponentProps<typeof MenuPrimitive.Content> & {\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n};\n\nexport function DropdownMenuContent({ sideOffset = 6, align = \"end\", collisionPadding = 8, container, className, ...props }: DropdownMenuContentProps) {\n return (\n <MenuPrimitive.Portal container={container}>\n <MenuPrimitive.Content sideOffset={sideOffset} align={align} collisionPadding={collisionPadding} className={s.content({ className })} {...props} />\n </MenuPrimitive.Portal>\n );\n}\n\nexport type DropdownMenuItemProps = React.ComponentProps<typeof MenuPrimitive.Item> & {\n /** `danger` for a destructive action: the icon and the highlight carry the danger, the label stays legible. */\n tone?: \"default\" | \"danger\";\n};\n\n/** An action. `tone=\"danger\"` for a destructive one; `onSelect` runs it (the menu closes). */\nexport function DropdownMenuItem({ tone, className, ...props }: DropdownMenuItemProps) {\n return <MenuPrimitive.Item data-tone={tone ?? \"default\"} className={menu({ tone }).item({ className })} {...props} />;\n}\n\n/** A toggled option: `checked` + `onCheckedChange`. */\nexport function DropdownMenuCheckboxItem({ className, children, ...props }: React.ComponentProps<typeof MenuPrimitive.CheckboxItem>) {\n return (\n <MenuPrimitive.CheckboxItem className={s.item({ className })} {...props}>\n {children}\n <MenuPrimitive.ItemIndicator className={s.indicator()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </MenuPrimitive.ItemIndicator>\n </MenuPrimitive.CheckboxItem>\n );\n}\n\nexport function DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Label>) {\n return <MenuPrimitive.Label className={s.label({ className })} {...props} />;\n}\n\nexport function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Separator>) {\n return <MenuPrimitive.Separator className={s.separator({ className })} {...props} />;\n}\n\n/** `DropdownMenu.Root/Trigger/Content/Item/CheckboxItem/Label/Separator/Group`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const DropdownMenu = {\n Root: MenuPrimitive.Root,\n Trigger: MenuPrimitive.Trigger,\n Content: DropdownMenuContent,\n Item: DropdownMenuItem,\n CheckboxItem: DropdownMenuCheckboxItem,\n Label: DropdownMenuLabel,\n Separator: DropdownMenuSeparator,\n Group: MenuPrimitive.Group,\n};\n"],"mappings":";;;;;;;;;;;;;AAIA,SAAS,gBAAgBA,sBAAqB;AAE9C,SAAS,MAAAC,WAAU;;;ACLnB,SAAS,gBAAgB,qBAAqB;AAE9C,SAAS,UAAU;AAiCb,cAkBF,YAlBE;AA5BC,IAAM,OAAO,GAAG;AAAA,EACrB,OAAO;AAAA,IACL,SAAS,GAAG,UAAU,6IAA6I;AAAA,IACnK,MACE;AAAA,IAGF,WAAW;AAAA,IACX,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA,UAAU;AAAA;AAAA;AAAA,IAGR,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,EAAE,MAAM,mEAAmE,EAAE;AAAA,EAC5G;AAAA,EACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CAAC;AACD,IAAM,IAAI,KAAK;AAOR,SAAS,oBAAoB,EAAE,aAAa,GAAG,QAAQ,OAAO,mBAAmB,GAAG,WAAW,WAAW,GAAG,MAAM,GAA6B;AACrJ,SACE,oBAAC,cAAc,QAAd,EAAqB,WACpB,8BAAC,cAAc,SAAd,EAAsB,YAAwB,OAAc,kBAAoC,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO,GACnJ;AAEJ;AAQO,SAAS,iBAAiB,EAAE,MAAM,WAAW,GAAG,MAAM,GAA0B;AACrF,SAAO,oBAAC,cAAc,MAAd,EAAmB,aAAW,QAAQ,WAAW,WAAW,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AACrH;AAGO,SAAS,yBAAyB,EAAE,WAAW,UAAU,GAAG,MAAM,GAA4D;AACnI,SACE,qBAAC,cAAc,cAAd,EAA2B,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAC/D;AAAA;AAAA,IACD,oBAAC,cAAc,eAAd,EAA4B,WAAW,EAAE,UAAU,GAClD,8BAAC,SAAI,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACnI,8BAAC,UAAK,GAAE,mBAAkB,GAC5B,GACF;AAAA,KACF;AAEJ;AAEO,SAAS,kBAAkB,EAAE,WAAW,GAAG,MAAM,GAAqD;AAC3G,SAAO,oBAAC,cAAc,OAAd,EAAoB,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AAC5E;AAEO,SAAS,sBAAsB,EAAE,WAAW,GAAG,MAAM,GAAyD;AACnH,SAAO,oBAAC,cAAc,WAAd,EAAwB,WAAW,EAAE,UAAU,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AACpF;AAGO,IAAM,eAAe;AAAA,EAC1B,MAAM,cAAc;AAAA,EACpB,SAAS,cAAc;AAAA,EACvB,SAAS;AAAA,EACT,MAAM;AAAA,EACN,cAAc;AAAA,EACd,OAAO;AAAA,EACP,WAAW;AAAA,EACX,OAAO,cAAc;AACvB;;;ADbkB,SAsEd,UAtEc,OAAAC,MAEV,QAAAC,aAFU;AA1DX,IAAM,YAAYC,IAAG;AAAA,EAC1B,OAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,OACE;AAAA,IACF,SAAS;AAAA,IACT,SACE;AAAA,IAGF,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AACF,CAAC;AA6BM,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AAAA,EACA,aAAa;AAAA,EACb,cAAc,CAAC,SAAS,UAAU,IAAI;AAAA,EACtC;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAMC,KAAI,UAAU;AACpB,SACE,gBAAAF,MAAC,SAAI,MAAK,SAAQ,cAAY,OAAO,WAAWE,GAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OACxE;AAAA,eAAW,gBAAAH,KAAC,SAAI,WAAWG,GAAE,SAAS,GAAI,UAAS,IAAS;AAAA,IAC5D,OAAO,SAAS,KAAK,YAAY,SAChC,gBAAAF,MAAC,SAAI,WAAWE,GAAE,OAAO,GACtB;AAAA,aAAO,IAAI,CAAC,WACX,gBAAAH,KAAC,QAAqB,MAAK,MAAK,WAAS,MAAC,aAAa,YAAY,OAAO,KAAK,GAAG,UAAU,OAAO,UAChG,iBAAO,SADC,OAAO,EAElB,CACD;AAAA,MACA,OAAO,SAAS,KAAK,aACpB,gBAAAA,KAAC,YAAO,MAAK,UAAS,WAAWG,GAAE,MAAM,GAAG,SAAS,YAClD,sBACH,IACE;AAAA,MACJ,gBAAAH,KAAC,OAAE,WAAWG,GAAE,QAAQ,GAAG,aAAU,UAAS,eAAW,MACtD,mBACH;AAAA,OACF,IACE;AAAA,KACN;AAEJ;AA2CO,SAAS,WAAW,OAAwB;AACjD,QAAM,EAAE,OAAO,SAAS,aAAa,UAAU,IAAI;AACnD,QAAMA,KAAI,UAAU;AACpB,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,MAAM,aAAa,QAAS,MAAM,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,IAAK,MAAM;AACrF,QAAM,SAAS,MAAM,aAAa,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,MAAM,KAAK,IAAI;AACzF,QAAM,UAAU,CAAC,WACf,gBAAAF,MAAA,YACG;AAAA,WAAO;AAAA,IACP,OAAO,UAAU,SAAY,gBAAAD,KAAC,UAAK,WAAWG,GAAE,YAAY,GAAI,iBAAO,OAAM,IAAU;AAAA,KAC1F;AAEF,SACE,gBAAAF,MAAC,aAAa,MAAb,EAAkB,aAA0B,OAAO,OAClD;AAAA,oBAAAA,MAAC,aAAa,SAAb,EAAqB,WAAWE,GAAE,QAAQ,EAAE,UAAU,CAAC,GAAG,eAAa,OAAO,SAAS,IAAI,KAAK,QAC9F;AAAA,eAAS,GAAG,KAAK,KAAK,OAAO,KAAK,KAAK;AAAA,MACvC,MAAM,aAAa,SAAS,OAAO,SAAS,IAAI,gBAAAH,KAAC,UAAK,WAAWG,GAAE,MAAM,GAAI,iBAAO,QAAO,IAAU;AAAA,MACtG,gBAAAH,KAAC,gBAAa,WAAU,WAAU;AAAA,OACpC;AAAA,IACA,gBAAAA,KAAC,aAAa,SAAb,EAAqB,OAAM,SAAQ,cAAY,OAC7C,gBAAM,aAAa,QAClB,gBAAAA,KAACI,eAAc,YAAd,EAAyB,OAAO,MAAM,OAAO,eAAe,MAAM,eAChE,kBAAQ,IAAI,CAAC,WACZ,gBAAAH,MAACG,eAAc,WAAd,EAA2C,OAAO,OAAO,OAAO,UAAU,OAAO,UAAU,WAAW,EAAE,KAAK,GAC5G;AAAA,sBAAAJ,KAAC,UAAK,WAAU,8FACd,0BAAAA,KAACI,eAAc,eAAd,EAA4B,WAAU,kCAAiC,GAC1E;AAAA,MACC,QAAQ,MAAM;AAAA,SAJa,OAAO,KAKrC,CACD,GACH,IAEA,QAAQ,IAAI,CAAC,WACX,gBAAAJ;AAAA,MAAC,aAAa;AAAA,MAAb;AAAA,QAEC,UAAU,OAAO;AAAA,QACjB,SAAS,MAAM,MAAM,SAAS,OAAO,KAAK;AAAA,QAC1C,UAAU,CAAC,UAAU,MAAM,eAAe;AAAA,QAC1C,iBAAiB,CAAC,YAChB,MAAM,cAAc,UAAU,CAAC,GAAG,MAAM,OAAO,OAAO,KAAK,IAAI,MAAM,MAAM,OAAO,CAAC,MAAM,MAAM,OAAO,KAAK,CAAC;AAAA,QAG7G,kBAAQ,MAAM;AAAA;AAAA,MARV,OAAO;AAAA,IASd,CACD,GAEL;AAAA,KACF;AAEJ;AAGO,SAAS,iBAAiB,QAAyB,MAAmD;AAC3G,SAAO,OAAO;AAAA,IACZ,KAAK,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,OAAO,GAAG,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC;AAAA,EAC/G;AACF;AAGO,SAAS,kBAAkB,MAAuB,SAAwE,WAAW,QAAQ;AAClJ,QAAM,OAAO,IAAI,gBAAgB,IAAI;AACrC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAM,OAAO,SAAS,OAAO,CAAC,IAAI,OAAO,UAAU,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,KAAK;AACjF,QAAI,KAAK,OAAO,OAAO,EAAE,WAAW,EAAG,MAAK,OAAO,GAAG;AAAA,QACjD,MAAK,IAAI,KAAK,KAAK,OAAO,OAAO,EAAE,KAAK,GAAG,CAAC;AAAA,EACnD;AACA,MAAI,SAAU,MAAK,OAAO,QAAQ;AAClC,SAAO;AACT;","names":["MenuPrimitive","tv","jsx","jsxs","tv","s","MenuPrimitive"]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/image-cropper.js
CHANGED
package/dist/media-capture.js
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { UseInfiniteQueryResult, InfiniteData } from '@krizaka/query';
|
|
2
|
+
import { FlashListRef } from '@shopify/flash-list';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
5
|
+
|
|
6
|
+
type ListProps<T> = {
|
|
7
|
+
/** The items (stable: a query's data, state). */
|
|
8
|
+
data: readonly T[];
|
|
9
|
+
/** Draws one item. */
|
|
10
|
+
renderItem: (item: T, index: number) => React.ReactElement | null;
|
|
11
|
+
/** A stable key per item (an id): FlashList recycles the views by it. */
|
|
12
|
+
keyExtractor: (item: T, index: number) => string;
|
|
13
|
+
/** The accessible name of the list ("Notifications"), passed translated. */
|
|
14
|
+
label: string;
|
|
15
|
+
/** A hairline between items: `full` (default), `inset` (aligned with the text after an avatar), `none`. */
|
|
16
|
+
separator?: "full" | "inset" | "none";
|
|
17
|
+
/** The first page is loading: skeleton rows (`loadingRows` of them) in place of the items. */
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
/** How many skeleton rows while loading. Default 6. */
|
|
20
|
+
loadingRows?: number;
|
|
21
|
+
/** A failure, shown in place of the items (an `ErrorState` with a retry). */
|
|
22
|
+
error?: React.ReactNode;
|
|
23
|
+
/** Shown when there is no item (an `EmptyState`, or its words: `{ title, description, action }`). */
|
|
24
|
+
empty?: React.ReactNode | {
|
|
25
|
+
title: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
action?: React.ReactNode;
|
|
28
|
+
};
|
|
29
|
+
/** The list is reloading after a pull: the system's spinner at the top. */
|
|
30
|
+
refreshing?: boolean;
|
|
31
|
+
/** Pull to refresh: called when the person pulls the list down. */
|
|
32
|
+
onRefresh?: () => void;
|
|
33
|
+
/** Called when the end comes near: load the next page. */
|
|
34
|
+
onEndReached?: () => void;
|
|
35
|
+
/** How far from the end `onEndReached` fires, in screens. Default 0.5. */
|
|
36
|
+
endReachedThreshold?: number;
|
|
37
|
+
/** The next page is loading: a spinner under the last item, named by `loadingMoreLabel`. */
|
|
38
|
+
loadingMore?: boolean;
|
|
39
|
+
/** The name of that spinner ("Loading more"), passed translated. */
|
|
40
|
+
loadingMoreLabel?: string;
|
|
41
|
+
/** Above the first item (a title, filters): scrolls with the list. */
|
|
42
|
+
header?: React.ReactElement;
|
|
43
|
+
/** Under the last item. */
|
|
44
|
+
footer?: React.ReactElement;
|
|
45
|
+
/** Extra data the items depend on (a selection): a change re-renders the visible items. */
|
|
46
|
+
extraData?: unknown;
|
|
47
|
+
/** The FlashList, for `scrollToIndex` / `scrollToOffset`. */
|
|
48
|
+
ref?: React.Ref<FlashListRef<T>>;
|
|
49
|
+
/** Styles of the list's content (padding). */
|
|
50
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
51
|
+
/** Styles merged last, on the list. */
|
|
52
|
+
style?: StyleProp<ViewStyle>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* A long list on FlashList v2 (recycled views, 60 fps): separators, skeleton rows while the first page loads, an empty
|
|
56
|
+
* or error state in place of the items, pull to refresh tinted with the theme, the next page asked for near the end and
|
|
57
|
+
* a spinner under the last item. With `useInfiniteList`, a paginated API is one hook and one component.
|
|
58
|
+
*/
|
|
59
|
+
declare function List<T>({ data, renderItem, keyExtractor, label, separator, loading, loadingRows, error, empty, refreshing, onRefresh, onEndReached, endReachedThreshold, loadingMore, loadingMoreLabel, header, footer, extraData, ref, contentContainerStyle, style, }: ListProps<T>): React.JSX.Element;
|
|
60
|
+
type InfiniteListOptions<TPage, TItem, TParam> = {
|
|
61
|
+
/** The query key (a `defineKeys` key): one cache entry for every page. */
|
|
62
|
+
queryKey: readonly unknown[];
|
|
63
|
+
/** Fetches one page (`krizakaFetch`), from its page parameter (a cursor, a page number). */
|
|
64
|
+
queryFn: (context: {
|
|
65
|
+
pageParam: TParam;
|
|
66
|
+
signal: AbortSignal;
|
|
67
|
+
}) => Promise<TPage>;
|
|
68
|
+
/** The parameter of the first page (`null` for a cursor, `1` for numbers). */
|
|
69
|
+
initialPageParam: TParam;
|
|
70
|
+
/** The parameter of the page after `lastPage`, or `undefined` / `null` when there is none. */
|
|
71
|
+
getNextPageParam: (lastPage: TPage, pages: TPage[]) => TParam | undefined | null;
|
|
72
|
+
/** The items of a page (`(page) => page.items`). */
|
|
73
|
+
getItems: (page: TPage) => readonly TItem[];
|
|
74
|
+
/** `false` to wait (a missing id). Default `true`. */
|
|
75
|
+
enabled?: boolean;
|
|
76
|
+
/** How long a page stays fresh, in ms (the client's default otherwise). */
|
|
77
|
+
staleTime?: number;
|
|
78
|
+
};
|
|
79
|
+
/** What `useInfiniteList` gives: the props of `List` to spread, and the query itself. */
|
|
80
|
+
type InfiniteListResult<TPage, TItem, TParam = unknown> = {
|
|
81
|
+
/** `{ data, loading, refreshing, onRefresh, onEndReached, loadingMore }` — spread on `<List>`. */
|
|
82
|
+
listProps: Pick<ListProps<TItem>, "data" | "loading" | "refreshing" | "onRefresh" | "onEndReached" | "loadingMore">;
|
|
83
|
+
/** The failure of the first page, if any: render an `ErrorState` in `error` (a later page's failure keeps the list). */
|
|
84
|
+
error: Error | null;
|
|
85
|
+
/** The query of @krizaka/query (`refetch`, `status`, the pages…). */
|
|
86
|
+
query: UseInfiniteQueryResult<InfiniteData<TPage, TParam>, Error>;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* A paginated API as a list: `useInfiniteQuery` of @krizaka/query (a list: `meta: { list: true }`, so an error stays in
|
|
90
|
+
* the list), the pages flattened, the next page asked for by `onEndReached` (once at a time, only when there is one),
|
|
91
|
+
* pull to refresh — spread `listProps` on `<List>`.
|
|
92
|
+
*/
|
|
93
|
+
declare function useInfiniteList<TPage, TItem, TParam = unknown>({ queryKey, queryFn, initialPageParam, getNextPageParam, getItems, enabled, staleTime, }: InfiniteListOptions<TPage, TItem, TParam>): InfiniteListResult<TPage, TItem, TParam>;
|
|
94
|
+
|
|
95
|
+
export { type InfiniteListOptions, type InfiniteListResult, List, type ListProps, useInfiniteList };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/native/list.tsx
|
|
2
|
+
import { useInfiniteQuery } from "@krizaka/query";
|
|
3
|
+
import { EmptyState, Skeleton, Spinner, useTheme } from "@krizaka/ui/native";
|
|
4
|
+
import { FlashList } from "@shopify/flash-list";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { RefreshControl, StyleSheet, View } from "react-native";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
function SkeletonRow() {
|
|
9
|
+
return /* @__PURE__ */ jsxs(View, { style: styles.skeleton, "aria-hidden": true, children: [
|
|
10
|
+
/* @__PURE__ */ jsx(Skeleton, { shape: "circle", width: 40, height: 40 }),
|
|
11
|
+
/* @__PURE__ */ jsxs(View, { style: styles.skeletonText, children: [
|
|
12
|
+
/* @__PURE__ */ jsx(Skeleton, { width: "60%" }),
|
|
13
|
+
/* @__PURE__ */ jsx(Skeleton, { width: "35%" })
|
|
14
|
+
] })
|
|
15
|
+
] });
|
|
16
|
+
}
|
|
17
|
+
var isWords = (value) => typeof value === "object" && value !== null && !React.isValidElement(value) && "title" in value;
|
|
18
|
+
function List({
|
|
19
|
+
data,
|
|
20
|
+
renderItem,
|
|
21
|
+
keyExtractor,
|
|
22
|
+
label,
|
|
23
|
+
separator = "full",
|
|
24
|
+
loading,
|
|
25
|
+
loadingRows = 6,
|
|
26
|
+
error,
|
|
27
|
+
empty,
|
|
28
|
+
refreshing = false,
|
|
29
|
+
onRefresh,
|
|
30
|
+
onEndReached,
|
|
31
|
+
endReachedThreshold = 0.5,
|
|
32
|
+
loadingMore,
|
|
33
|
+
loadingMoreLabel = "Loading more",
|
|
34
|
+
header,
|
|
35
|
+
footer,
|
|
36
|
+
extraData,
|
|
37
|
+
ref,
|
|
38
|
+
contentContainerStyle,
|
|
39
|
+
style
|
|
40
|
+
}) {
|
|
41
|
+
const { theme } = useTheme();
|
|
42
|
+
const Separator = React.useMemo(
|
|
43
|
+
() => separator === "none" ? void 0 : function ListSeparator() {
|
|
44
|
+
return /* @__PURE__ */ jsx(View, { style: [styles.separator, { backgroundColor: theme.borderSubtle }, separator === "inset" && styles.inset] });
|
|
45
|
+
},
|
|
46
|
+
[separator, theme.borderSubtle]
|
|
47
|
+
);
|
|
48
|
+
if (loading) {
|
|
49
|
+
return /* @__PURE__ */ jsxs(View, { role: "group", style: [styles.fill, style], "aria-busy": true, accessibilityLabel: label, children: [
|
|
50
|
+
header,
|
|
51
|
+
Array.from({ length: loadingRows }, (_, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
52
|
+
i > 0 && Separator ? /* @__PURE__ */ jsx(Separator, {}) : null,
|
|
53
|
+
/* @__PURE__ */ jsx(SkeletonRow, {})
|
|
54
|
+
] }, i))
|
|
55
|
+
] });
|
|
56
|
+
}
|
|
57
|
+
const state = error ? /* @__PURE__ */ jsx(View, { role: "alert", style: styles.state, children: error }) : isWords(empty) ? /* @__PURE__ */ jsx(EmptyState, { title: empty.title, description: empty.description, action: empty.action, style: styles.state }) : empty ? /* @__PURE__ */ jsx(View, { style: styles.state, children: empty }) : null;
|
|
58
|
+
return /* @__PURE__ */ jsx(
|
|
59
|
+
FlashList,
|
|
60
|
+
{
|
|
61
|
+
ref,
|
|
62
|
+
accessibilityLabel: label,
|
|
63
|
+
focusable: true,
|
|
64
|
+
data: error ? [] : data,
|
|
65
|
+
renderItem: ({ item, index }) => renderItem(item, index),
|
|
66
|
+
keyExtractor,
|
|
67
|
+
extraData,
|
|
68
|
+
ItemSeparatorComponent: Separator,
|
|
69
|
+
ListHeaderComponent: header,
|
|
70
|
+
ListEmptyComponent: state,
|
|
71
|
+
ListFooterComponent: loadingMore || footer ? /* @__PURE__ */ jsxs(View, { children: [
|
|
72
|
+
loadingMore ? /* @__PURE__ */ jsx(View, { style: styles.more, children: /* @__PURE__ */ jsx(Spinner, { label: loadingMoreLabel }) }) : null,
|
|
73
|
+
footer
|
|
74
|
+
] }) : null,
|
|
75
|
+
onEndReached,
|
|
76
|
+
onEndReachedThreshold: endReachedThreshold,
|
|
77
|
+
refreshControl: onRefresh ? /* @__PURE__ */ jsx(RefreshControl, { refreshing, onRefresh, tintColor: theme.accent, colors: [theme.accent], progressBackgroundColor: theme.surface1 }) : void 0,
|
|
78
|
+
contentContainerStyle,
|
|
79
|
+
style
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function useInfiniteList({
|
|
84
|
+
queryKey,
|
|
85
|
+
queryFn,
|
|
86
|
+
initialPageParam,
|
|
87
|
+
getNextPageParam,
|
|
88
|
+
getItems,
|
|
89
|
+
enabled,
|
|
90
|
+
staleTime
|
|
91
|
+
}) {
|
|
92
|
+
const query = useInfiniteQuery({
|
|
93
|
+
queryKey,
|
|
94
|
+
queryFn: ({ pageParam, signal }) => queryFn({ pageParam, signal }),
|
|
95
|
+
initialPageParam,
|
|
96
|
+
getNextPageParam: (last, pages2) => getNextPageParam(last, pages2) ?? void 0,
|
|
97
|
+
enabled,
|
|
98
|
+
staleTime,
|
|
99
|
+
meta: { list: true }
|
|
100
|
+
});
|
|
101
|
+
const [pulled, setPulled] = React.useState(false);
|
|
102
|
+
const { data: pages, hasNextPage, isFetchingNextPage, fetchNextPage, refetch, isPending, isError, isRefetching } = query;
|
|
103
|
+
const data = React.useMemo(() => (pages?.pages ?? []).flatMap((page) => getItems(page)), [pages]);
|
|
104
|
+
return {
|
|
105
|
+
listProps: {
|
|
106
|
+
data,
|
|
107
|
+
loading: isPending && enabled !== false,
|
|
108
|
+
refreshing: pulled && isRefetching,
|
|
109
|
+
onRefresh: () => {
|
|
110
|
+
setPulled(true);
|
|
111
|
+
void refetch().finally(() => setPulled(false));
|
|
112
|
+
},
|
|
113
|
+
onEndReached: () => {
|
|
114
|
+
if (hasNextPage && !isFetchingNextPage && !isError) void fetchNextPage();
|
|
115
|
+
},
|
|
116
|
+
loadingMore: isFetchingNextPage
|
|
117
|
+
},
|
|
118
|
+
error: isError && !pages ? query.error : null,
|
|
119
|
+
query
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
var styles = StyleSheet.create({
|
|
123
|
+
fill: { flex: 1 },
|
|
124
|
+
separator: { height: StyleSheet.hairlineWidth },
|
|
125
|
+
inset: { marginLeft: 68 },
|
|
126
|
+
skeleton: { flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 16, paddingVertical: 14 },
|
|
127
|
+
skeletonText: { flex: 1, gap: 8 },
|
|
128
|
+
state: { paddingHorizontal: 16, paddingVertical: 32 },
|
|
129
|
+
more: { paddingVertical: 20, alignItems: "center" }
|
|
130
|
+
});
|
|
131
|
+
export {
|
|
132
|
+
List,
|
|
133
|
+
useInfiniteList
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/native/list.tsx"],"sourcesContent":["// @krizaka/ui/native/list — its own entry, because it imports FlashList v2 (@shopify/flash-list) and @krizaka/query:\n// optional peers, installed only by the apps that list. The theme and the states come from @krizaka/ui/native (one\n// theme context); the server state comes from @krizaka/query (one QueryClient context — never @tanstack/* here).\nimport { type InfiniteData, useInfiniteQuery, type UseInfiniteQueryResult } from \"@krizaka/query\";\nimport { EmptyState, Skeleton, Spinner, useTheme } from \"@krizaka/ui/native\";\nimport { FlashList, type FlashListRef } from \"@shopify/flash-list\";\nimport * as React from \"react\";\nimport { RefreshControl, type StyleProp, StyleSheet, View, type ViewStyle } from \"react-native\";\n\nexport type ListProps<T> = {\n /** The items (stable: a query's data, state). */\n data: readonly T[];\n /** Draws one item. */\n renderItem: (item: T, index: number) => React.ReactElement | null;\n /** A stable key per item (an id): FlashList recycles the views by it. */\n keyExtractor: (item: T, index: number) => string;\n /** The accessible name of the list (\"Notifications\"), passed translated. */\n label: string;\n /** A hairline between items: `full` (default), `inset` (aligned with the text after an avatar), `none`. */\n separator?: \"full\" | \"inset\" | \"none\";\n /** The first page is loading: skeleton rows (`loadingRows` of them) in place of the items. */\n loading?: boolean;\n /** How many skeleton rows while loading. Default 6. */\n loadingRows?: number;\n /** A failure, shown in place of the items (an `ErrorState` with a retry). */\n error?: React.ReactNode;\n /** Shown when there is no item (an `EmptyState`, or its words: `{ title, description, action }`). */\n empty?: React.ReactNode | { title: string; description?: string; action?: React.ReactNode };\n /** The list is reloading after a pull: the system's spinner at the top. */\n refreshing?: boolean;\n /** Pull to refresh: called when the person pulls the list down. */\n onRefresh?: () => void;\n /** Called when the end comes near: load the next page. */\n onEndReached?: () => void;\n /** How far from the end `onEndReached` fires, in screens. Default 0.5. */\n endReachedThreshold?: number;\n /** The next page is loading: a spinner under the last item, named by `loadingMoreLabel`. */\n loadingMore?: boolean;\n /** The name of that spinner (\"Loading more\"), passed translated. */\n loadingMoreLabel?: string;\n /** Above the first item (a title, filters): scrolls with the list. */\n header?: React.ReactElement;\n /** Under the last item. */\n footer?: React.ReactElement;\n /** Extra data the items depend on (a selection): a change re-renders the visible items. */\n extraData?: unknown;\n /** The FlashList, for `scrollToIndex` / `scrollToOffset`. */\n ref?: React.Ref<FlashListRef<T>>;\n /** Styles of the list's content (padding). */\n contentContainerStyle?: StyleProp<ViewStyle>;\n /** Styles merged last, on the list. */\n style?: StyleProp<ViewStyle>;\n};\n\nfunction SkeletonRow() {\n return (\n <View style={styles.skeleton} aria-hidden>\n <Skeleton shape=\"circle\" width={40} height={40} />\n <View style={styles.skeletonText}>\n <Skeleton width=\"60%\" />\n <Skeleton width=\"35%\" />\n </View>\n </View>\n );\n}\n\nconst isWords = (value: unknown): value is { title: string; description?: string; action?: React.ReactNode } =>\n typeof value === \"object\" && value !== null && !React.isValidElement(value) && \"title\" in value;\n\n/**\n * A long list on FlashList v2 (recycled views, 60 fps): separators, skeleton rows while the first page loads, an empty\n * or error state in place of the items, pull to refresh tinted with the theme, the next page asked for near the end and\n * a spinner under the last item. With `useInfiniteList`, a paginated API is one hook and one component.\n */\nexport function List<T>({\n data,\n renderItem,\n keyExtractor,\n label,\n separator = \"full\",\n loading,\n loadingRows = 6,\n error,\n empty,\n refreshing = false,\n onRefresh,\n onEndReached,\n endReachedThreshold = 0.5,\n loadingMore,\n loadingMoreLabel = \"Loading more\",\n header,\n footer,\n extraData,\n ref,\n contentContainerStyle,\n style,\n}: ListProps<T>) {\n const { theme } = useTheme();\n const Separator = React.useMemo(\n () =>\n separator === \"none\"\n ? undefined\n : function ListSeparator() {\n return <View style={[styles.separator, { backgroundColor: theme.borderSubtle }, separator === \"inset\" && styles.inset]} />;\n },\n [separator, theme.borderSubtle],\n );\n\n if (loading) {\n return (\n <View role=\"group\" style={[styles.fill, style]} aria-busy accessibilityLabel={label}>\n {header}\n {Array.from({ length: loadingRows }, (_, i) => (\n <React.Fragment key={i}>\n {i > 0 && Separator ? <Separator /> : null}\n <SkeletonRow />\n </React.Fragment>\n ))}\n </View>\n );\n }\n\n const state = error ? (\n <View role=\"alert\" style={styles.state}>\n {error}\n </View>\n ) : isWords(empty) ? (\n <EmptyState title={empty.title} description={empty.description} action={empty.action} style={styles.state} />\n ) : empty ? (\n <View style={styles.state}>{empty}</View>\n ) : null;\n\n return (\n <FlashList\n ref={ref}\n accessibilityLabel={label}\n // Reachable from a keyboard (a hardware keyboard, the web): a scrolling area takes the focus to be scrolled.\n focusable\n data={error ? [] : data}\n renderItem={({ item, index }) => renderItem(item, index)}\n keyExtractor={keyExtractor}\n extraData={extraData}\n ItemSeparatorComponent={Separator}\n ListHeaderComponent={header}\n ListEmptyComponent={state}\n ListFooterComponent={\n loadingMore || footer ? (\n <View>\n {loadingMore ? (\n <View style={styles.more}>\n <Spinner label={loadingMoreLabel} />\n </View>\n ) : null}\n {footer}\n </View>\n ) : null\n }\n onEndReached={onEndReached}\n onEndReachedThreshold={endReachedThreshold}\n refreshControl={\n onRefresh ? (\n <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.accent} colors={[theme.accent]} progressBackgroundColor={theme.surface1} />\n ) : undefined\n }\n contentContainerStyle={contentContainerStyle as never}\n style={style}\n />\n );\n}\n\nexport type InfiniteListOptions<TPage, TItem, TParam> = {\n /** The query key (a `defineKeys` key): one cache entry for every page. */\n queryKey: readonly unknown[];\n /** Fetches one page (`krizakaFetch`), from its page parameter (a cursor, a page number). */\n queryFn: (context: { pageParam: TParam; signal: AbortSignal }) => Promise<TPage>;\n /** The parameter of the first page (`null` for a cursor, `1` for numbers). */\n initialPageParam: TParam;\n /** The parameter of the page after `lastPage`, or `undefined` / `null` when there is none. */\n getNextPageParam: (lastPage: TPage, pages: TPage[]) => TParam | undefined | null;\n /** The items of a page (`(page) => page.items`). */\n getItems: (page: TPage) => readonly TItem[];\n /** `false` to wait (a missing id). Default `true`. */\n enabled?: boolean;\n /** How long a page stays fresh, in ms (the client's default otherwise). */\n staleTime?: number;\n};\n\n/** What `useInfiniteList` gives: the props of `List` to spread, and the query itself. */\nexport type InfiniteListResult<TPage, TItem, TParam = unknown> = {\n /** `{ data, loading, refreshing, onRefresh, onEndReached, loadingMore }` — spread on `<List>`. */\n listProps: Pick<ListProps<TItem>, \"data\" | \"loading\" | \"refreshing\" | \"onRefresh\" | \"onEndReached\" | \"loadingMore\">;\n /** The failure of the first page, if any: render an `ErrorState` in `error` (a later page's failure keeps the list). */\n error: Error | null;\n /** The query of @krizaka/query (`refetch`, `status`, the pages…). */\n query: UseInfiniteQueryResult<InfiniteData<TPage, TParam>, Error>;\n};\n\n/**\n * A paginated API as a list: `useInfiniteQuery` of @krizaka/query (a list: `meta: { list: true }`, so an error stays in\n * the list), the pages flattened, the next page asked for by `onEndReached` (once at a time, only when there is one),\n * pull to refresh — spread `listProps` on `<List>`.\n */\nexport function useInfiniteList<TPage, TItem, TParam = unknown>({\n queryKey,\n queryFn,\n initialPageParam,\n getNextPageParam,\n getItems,\n enabled,\n staleTime,\n}: InfiniteListOptions<TPage, TItem, TParam>): InfiniteListResult<TPage, TItem, TParam> {\n const query = useInfiniteQuery<TPage, Error, InfiniteData<TPage, TParam>, readonly unknown[], TParam>({\n queryKey,\n queryFn: ({ pageParam, signal }) => queryFn({ pageParam: pageParam as TParam, signal }),\n initialPageParam,\n getNextPageParam: (last, pages) => getNextPageParam(last, pages) ?? undefined,\n enabled,\n staleTime,\n meta: { list: true },\n });\n const [pulled, setPulled] = React.useState(false);\n const { data: pages, hasNextPage, isFetchingNextPage, fetchNextPage, refetch, isPending, isError, isRefetching } = query;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `getItems` is a projection; the pages decide\n const data = React.useMemo(() => (pages?.pages ?? []).flatMap((page) => getItems(page)), [pages]);\n return {\n listProps: {\n data,\n loading: isPending && enabled !== false,\n refreshing: pulled && isRefetching,\n onRefresh: () => {\n setPulled(true);\n void refetch().finally(() => setPulled(false));\n },\n onEndReached: () => {\n if (hasNextPage && !isFetchingNextPage && !isError) void fetchNextPage();\n },\n loadingMore: isFetchingNextPage,\n },\n error: isError && !pages ? query.error : null,\n query,\n };\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n separator: { height: StyleSheet.hairlineWidth },\n inset: { marginLeft: 68 },\n skeleton: { flexDirection: \"row\", alignItems: \"center\", gap: 12, paddingHorizontal: 16, paddingVertical: 14 },\n skeletonText: { flex: 1, gap: 8 },\n state: { paddingHorizontal: 16, paddingVertical: 32 },\n more: { paddingVertical: 20, alignItems: \"center\" },\n});\n"],"mappings":"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","names":["pages"]}
|
package/dist/search-field.js
CHANGED
|
@@ -1,165 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
CloseGlyph,
|
|
11
|
-
SearchGlyph
|
|
12
|
-
} from "./chunk-CPA5CF35.js";
|
|
13
|
-
|
|
14
|
-
// src/search-field/search-field.tsx
|
|
15
|
-
import * as React from "react";
|
|
16
|
-
import { tv as tv2 } from "tailwind-variants";
|
|
17
|
-
|
|
18
|
-
// src/kbd/kbd.tsx
|
|
19
|
-
import { tv } from "tailwind-variants";
|
|
20
|
-
import { jsx } from "react/jsx-runtime";
|
|
21
|
-
var kbdVariants = tv({
|
|
22
|
-
base: "inline-flex shrink-0 items-center justify-center rounded-md border border-border-default border-b-2 bg-surface-2 font-mono font-semibold text-fg-secondary",
|
|
23
|
-
variants: { size: { sm: "h-5 min-w-5 px-1 text-[10px]", md: "h-6 min-w-6 px-1.5 text-xs" } },
|
|
24
|
-
defaultVariants: { size: "md" }
|
|
25
|
-
});
|
|
26
|
-
function Kbd({ size, className, ...props }) {
|
|
27
|
-
return /* @__PURE__ */ jsx("kbd", { className: kbdVariants({ size, className }), ...props });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// src/search-field/search-field.tsx
|
|
31
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
32
|
-
var searchField = tv2({
|
|
33
|
-
slots: {
|
|
34
|
-
root: "relative w-full",
|
|
35
|
-
control: "flex h-11 w-full items-center gap-2 rounded-lg border border-border-default bg-surface-2 pl-3 pr-1.5 text-sm text-fg transition-[border-color,box-shadow] focus-within:border-accent focus-within:ring-2 focus-within:ring-ring",
|
|
36
|
-
icon: "h-4 w-4 shrink-0 text-fg-secondary",
|
|
37
|
-
input: "h-full min-w-0 flex-1 bg-transparent text-sm text-fg outline-hidden placeholder:text-fg-muted [&::-webkit-search-cancel-button]:appearance-none",
|
|
38
|
-
clear: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-fg-secondary hover:bg-surface-3 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
|
39
|
-
shortcut: "hidden shrink-0 items-center gap-0.5 pr-1 sm:flex",
|
|
40
|
-
list: "absolute inset-x-0 top-full z-50 mt-1 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-surface-2 p-1 shadow-lg",
|
|
41
|
-
option: "flex cursor-pointer select-none items-center gap-2.5 rounded-md px-2.5 py-2 text-sm text-fg data-[active]:bg-surface-3",
|
|
42
|
-
group: "px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase text-fg-muted"
|
|
43
|
-
},
|
|
44
|
-
variants: {
|
|
45
|
-
size: { md: {}, sm: { control: "h-9", clear: "h-7 w-7" } }
|
|
46
|
-
},
|
|
47
|
-
defaultVariants: { size: "md" }
|
|
48
|
-
});
|
|
49
|
-
var isApple = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
|
|
50
|
-
function SearchField({
|
|
51
|
-
label,
|
|
52
|
-
value: valueProp,
|
|
53
|
-
defaultValue = "",
|
|
54
|
-
onValueChange,
|
|
55
|
-
onSearch,
|
|
56
|
-
debounce = 250,
|
|
57
|
-
onSubmit,
|
|
58
|
-
shortcut = false,
|
|
59
|
-
suggestions,
|
|
60
|
-
suggestionsLabel,
|
|
61
|
-
onSuggestionSelect,
|
|
62
|
-
clearLabel = "Clear search",
|
|
63
|
-
landmark = true,
|
|
64
|
-
size,
|
|
65
|
-
className,
|
|
66
|
-
placeholder,
|
|
67
|
-
...props
|
|
68
|
-
}) {
|
|
69
|
-
const s = searchField({ size });
|
|
70
|
-
const [value, setValue] = useControllable(valueProp, defaultValue, onValueChange);
|
|
71
|
-
const [open, setOpen] = React.useState(false);
|
|
72
|
-
const [apple, setApple] = React.useState(false);
|
|
73
|
-
const input = React.useRef(null);
|
|
74
|
-
const search = useDebouncedCallback((query) => onSearch?.(query), debounce);
|
|
75
|
-
const list = suggestions ?? [];
|
|
76
|
-
const pick = (i) => {
|
|
77
|
-
const suggestion = list[i];
|
|
78
|
-
if (!suggestion) return;
|
|
79
|
-
setOpen(false);
|
|
80
|
-
onSuggestionSelect?.(suggestion);
|
|
81
|
-
};
|
|
82
|
-
const listbox = useListbox({ count: list.length, onPick: pick, open: open && list.length > 0, setOpen });
|
|
83
|
-
React.useEffect(() => {
|
|
84
|
-
if (!shortcut) return;
|
|
85
|
-
setApple(isApple());
|
|
86
|
-
const onKey = (event) => {
|
|
87
|
-
if (event.key.toLowerCase() === shortcut.toLowerCase() && (event.metaKey || event.ctrlKey) && !event.altKey) {
|
|
88
|
-
event.preventDefault();
|
|
89
|
-
input.current?.focus();
|
|
90
|
-
input.current?.select();
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
window.addEventListener("keydown", onKey);
|
|
94
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
95
|
-
}, [shortcut]);
|
|
96
|
-
const change = (query) => {
|
|
97
|
-
setValue(query);
|
|
98
|
-
search(query);
|
|
99
|
-
listbox.setActive(-1);
|
|
100
|
-
setOpen(true);
|
|
101
|
-
};
|
|
102
|
-
const field = /* @__PURE__ */ jsxs("div", { className: s.root({ className }), onBlur: (event) => !event.currentTarget.contains(event.relatedTarget) && setOpen(false), children: [
|
|
103
|
-
/* @__PURE__ */ jsxs("div", { className: s.control(), children: [
|
|
104
|
-
/* @__PURE__ */ jsx2(SearchGlyph, { className: s.icon() }),
|
|
105
|
-
/* @__PURE__ */ jsx2(
|
|
106
|
-
"input",
|
|
107
|
-
{
|
|
108
|
-
ref: input,
|
|
109
|
-
type: "search",
|
|
110
|
-
"aria-label": label,
|
|
111
|
-
enterKeyHint: "search",
|
|
112
|
-
autoComplete: "off",
|
|
113
|
-
...list.length > 0 ? listbox.inputProps : {},
|
|
114
|
-
value,
|
|
115
|
-
placeholder,
|
|
116
|
-
className: s.input(),
|
|
117
|
-
onChange: (event) => change(event.target.value),
|
|
118
|
-
onFocus: () => setOpen(true),
|
|
119
|
-
onKeyDown: (event) => {
|
|
120
|
-
if (list.length > 0 && listbox.onKeyDown(event)) return;
|
|
121
|
-
if (event.key === "Enter") {
|
|
122
|
-
event.preventDefault();
|
|
123
|
-
search.cancel();
|
|
124
|
-
onSearch?.(value);
|
|
125
|
-
onSubmit?.(value);
|
|
126
|
-
setOpen(false);
|
|
127
|
-
} else if (event.key === "Escape" && value) {
|
|
128
|
-
event.preventDefault();
|
|
129
|
-
change("");
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
...props
|
|
133
|
-
}
|
|
134
|
-
),
|
|
135
|
-
value ? /* @__PURE__ */ jsx2(
|
|
136
|
-
"button",
|
|
137
|
-
{
|
|
138
|
-
type: "button",
|
|
139
|
-
"aria-label": clearLabel,
|
|
140
|
-
className: s.clear(),
|
|
141
|
-
onClick: () => {
|
|
142
|
-
change("");
|
|
143
|
-
input.current?.focus();
|
|
144
|
-
},
|
|
145
|
-
children: /* @__PURE__ */ jsx2(CloseGlyph, { className: "h-4 w-4" })
|
|
146
|
-
}
|
|
147
|
-
) : shortcut ? /* @__PURE__ */ jsxs("span", { className: s.shortcut(), "aria-hidden": true, children: [
|
|
148
|
-
/* @__PURE__ */ jsx2(Kbd, { size: "sm", children: apple ? "\u2318" : "Ctrl" }),
|
|
149
|
-
/* @__PURE__ */ jsx2(Kbd, { size: "sm", children: shortcut.toUpperCase() })
|
|
150
|
-
] }) : null
|
|
151
|
-
] }),
|
|
152
|
-
list.length > 0 && /* @__PURE__ */ jsxs("div", { hidden: !open, className: s.list(), children: [
|
|
153
|
-
suggestionsLabel ? /* @__PURE__ */ jsx2("div", { "aria-hidden": true, className: s.group(), children: suggestionsLabel }) : null,
|
|
154
|
-
/* @__PURE__ */ jsx2("div", { id: listbox.listId, role: "listbox", "aria-label": suggestionsLabel ?? label, children: list.map((suggestion, i) => /* @__PURE__ */ jsxs("div", { ...listbox.optionProps(i, false), className: s.option(), children: [
|
|
155
|
-
/* @__PURE__ */ jsx2(SearchGlyph, { className: "h-3.5 w-3.5 shrink-0 text-fg-muted" }),
|
|
156
|
-
/* @__PURE__ */ jsx2("span", { className: "min-w-0 flex-1 truncate", children: suggestion.label }),
|
|
157
|
-
suggestion.meta ? /* @__PURE__ */ jsx2("span", { className: "shrink-0 text-xs text-fg-secondary", children: suggestion.meta }) : null
|
|
158
|
-
] }, suggestion.value)) })
|
|
159
|
-
] })
|
|
160
|
-
] });
|
|
161
|
-
return landmark ? /* @__PURE__ */ jsx2("div", { role: "search", children: field }) : field;
|
|
162
|
-
}
|
|
3
|
+
SearchField,
|
|
4
|
+
searchField
|
|
5
|
+
} from "./chunk-FDCMBWUU.js";
|
|
6
|
+
import "./chunk-ZIXB33X7.js";
|
|
7
|
+
import "./chunk-BRKMQ6JV.js";
|
|
8
|
+
import "./chunk-CPA5CF35.js";
|
|
163
9
|
export {
|
|
164
10
|
SearchField,
|
|
165
11
|
searchField
|
package/dist/search-field.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/search-field/search-field.tsx","../src/kbd/kbd.tsx"],"sourcesContent":["// Client. A search input (`type=\"search\"` inside a `role=\"search\"` landmark when it stands alone): a leading glyph,\n// a clear button, a global shortcut that focuses it (⌘K on Apple devices, Ctrl+K elsewhere, shown as `Kbd`), and\n// optional suggestions through the shared combobox engine. `onSearch` is debounced; Enter submits.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { CloseGlyph, SearchGlyph } from \"../glyphs\";\nimport { Kbd } from \"../kbd\";\nimport { useListbox } from \"../listbox\";\nimport { useControllable, useDebouncedCallback } from \"../media\";\n\nexport const searchField = tv({\n slots: {\n root: \"relative w-full\",\n control:\n \"flex h-11 w-full items-center gap-2 rounded-lg border border-border-default bg-surface-2 pl-3 pr-1.5 text-sm text-fg \" +\n \"transition-[border-color,box-shadow] focus-within:border-accent focus-within:ring-2 focus-within:ring-ring\",\n icon: \"h-4 w-4 shrink-0 text-fg-secondary\",\n input:\n \"h-full min-w-0 flex-1 bg-transparent text-sm text-fg outline-hidden placeholder:text-fg-muted \" +\n \"[&::-webkit-search-cancel-button]:appearance-none\",\n clear:\n \"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-fg-secondary hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n shortcut: \"hidden shrink-0 items-center gap-0.5 pr-1 sm:flex\",\n list: \"absolute inset-x-0 top-full z-50 mt-1 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-surface-2 p-1 shadow-lg\",\n option: \"flex cursor-pointer select-none items-center gap-2.5 rounded-md px-2.5 py-2 text-sm text-fg data-[active]:bg-surface-3\",\n group: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase text-fg-muted\",\n },\n variants: {\n size: { md: {}, sm: { control: \"h-9\", clear: \"h-7 w-7\" } },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type SearchSuggestion = {\n /** What `onSuggestionSelect` receives. */\n value: string;\n /** What the person reads. */\n label: string;\n /** A small text on the right (\"Creator\", \"12 videos\"). */\n meta?: string;\n};\n\nexport type SearchFieldProps = Omit<React.ComponentProps<\"input\">, \"value\" | \"defaultValue\" | \"onChange\" | \"onSubmit\" | \"size\" | \"type\"> & {\n /** The accessible name of the input (\"Search videos\"), passed translated. */\n label: string;\n /** The query (controlled). */\n value?: string;\n /** The query at first, uncontrolled. */\n defaultValue?: string;\n /** Called on every key with the query. */\n onValueChange?: (query: string) => void;\n /** Called with the query once typing pauses (`debounce` ms): fetch the results here. */\n onSearch?: (query: string) => void;\n /** The pause, in ms, before `onSearch`. Default 250. */\n debounce?: number;\n /** Called on Enter (no suggestion active) with the query: go to the results page. */\n onSubmit?: (query: string) => void;\n /** The global key that focuses the field with ⌘ / Ctrl (`\"k\"`), or `false` for none. Default `false`. */\n shortcut?: string | false;\n /** Suggestions under the field (recent searches, completions). */\n suggestions?: readonly SearchSuggestion[];\n /** A title above the suggestions (\"Recent searches\"). */\n suggestionsLabel?: string;\n /** Called with a suggestion picked (Enter on it, or a click). */\n onSuggestionSelect?: (suggestion: SearchSuggestion) => void;\n /** The clear button's name (\"Clear search\"), passed translated. */\n clearLabel?: string;\n /** Wraps the field in a `role=\"search\"` landmark (default) — `false` inside a form that already is one. */\n landmark?: boolean;\n /** md (default) · sm. */\n size?: \"md\" | \"sm\";\n};\n\nconst isApple = () => typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\n\n/** A search box: clear, shortcut, debounced search, submit on Enter, suggestions with the keyboard. */\nexport function SearchField({\n label,\n value: valueProp,\n defaultValue = \"\",\n onValueChange,\n onSearch,\n debounce = 250,\n onSubmit,\n shortcut = false,\n suggestions,\n suggestionsLabel,\n onSuggestionSelect,\n clearLabel = \"Clear search\",\n landmark = true,\n size,\n className,\n placeholder,\n ...props\n}: SearchFieldProps) {\n const s = searchField({ size });\n const [value, setValue] = useControllable(valueProp, defaultValue, onValueChange);\n const [open, setOpen] = React.useState(false);\n const [apple, setApple] = React.useState(false);\n const input = React.useRef<HTMLInputElement>(null);\n const search = useDebouncedCallback((query: string) => onSearch?.(query), debounce);\n const list = suggestions ?? [];\n\n const pick = (i: number) => {\n const suggestion = list[i];\n if (!suggestion) return;\n setOpen(false);\n onSuggestionSelect?.(suggestion);\n };\n const listbox = useListbox({ count: list.length, onPick: pick, open: open && list.length > 0, setOpen });\n\n React.useEffect(() => {\n if (!shortcut) return;\n // eslint-disable-next-line react-hooks/set-state-in-effect -- the platform is only known in the browser (no hydration mismatch)\n setApple(isApple());\n const onKey = (event: KeyboardEvent) => {\n if (event.key.toLowerCase() === shortcut.toLowerCase() && (event.metaKey || event.ctrlKey) && !event.altKey) {\n event.preventDefault();\n input.current?.focus();\n input.current?.select();\n }\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [shortcut]);\n\n const change = (query: string) => {\n setValue(query);\n search(query);\n listbox.setActive(-1);\n setOpen(true);\n };\n\n const field = (\n <div className={s.root({ className })} onBlur={(event) => !event.currentTarget.contains(event.relatedTarget as Node | null) && setOpen(false)}>\n <div className={s.control()}>\n <SearchGlyph className={s.icon()} />\n <input\n ref={input}\n type=\"search\"\n aria-label={label}\n enterKeyHint=\"search\"\n autoComplete=\"off\"\n {...(list.length > 0 ? listbox.inputProps : {})}\n value={value}\n placeholder={placeholder}\n className={s.input()}\n onChange={(event) => change(event.target.value)}\n onFocus={() => setOpen(true)}\n onKeyDown={(event) => {\n if (list.length > 0 && listbox.onKeyDown(event)) return;\n if (event.key === \"Enter\") {\n event.preventDefault();\n search.cancel();\n onSearch?.(value);\n onSubmit?.(value);\n setOpen(false);\n } else if (event.key === \"Escape\" && value) {\n event.preventDefault();\n change(\"\");\n }\n }}\n {...props}\n />\n {value ? (\n <button\n type=\"button\"\n aria-label={clearLabel}\n className={s.clear()}\n onClick={() => {\n change(\"\");\n input.current?.focus();\n }}\n >\n <CloseGlyph className=\"h-4 w-4\" />\n </button>\n ) : shortcut ? (\n <span className={s.shortcut()} aria-hidden>\n <Kbd size=\"sm\">{apple ? \"⌘\" : \"Ctrl\"}</Kbd>\n <Kbd size=\"sm\">{shortcut.toUpperCase()}</Kbd>\n </span>\n ) : null}\n </div>\n {list.length > 0 && (\n <div hidden={!open} className={s.list()}>\n {suggestionsLabel ? (\n <div aria-hidden className={s.group()}>\n {suggestionsLabel}\n </div>\n ) : null}\n <div id={listbox.listId} role=\"listbox\" aria-label={suggestionsLabel ?? label}>\n {list.map((suggestion, i) => (\n <div key={suggestion.value} {...listbox.optionProps(i, false)} className={s.option()}>\n <SearchGlyph className=\"h-3.5 w-3.5 shrink-0 text-fg-muted\" />\n <span className=\"min-w-0 flex-1 truncate\">{suggestion.label}</span>\n {suggestion.meta ? <span className=\"shrink-0 text-xs text-fg-secondary\">{suggestion.meta}</span> : null}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n );\n return landmark ? <div role=\"search\">{field}</div> : field;\n}\n","// Server-safe. A key, or a shortcut of several (`<Kbd>⌘</Kbd><Kbd>K</Kbd>`).\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const kbdVariants = tv({\n base:\n \"inline-flex shrink-0 items-center justify-center rounded-md border border-border-default border-b-2 bg-surface-2 \" +\n \"font-mono font-semibold text-fg-secondary\",\n variants: { size: { sm: \"h-5 min-w-5 px-1 text-[10px]\", md: \"h-6 min-w-6 px-1.5 text-xs\" } },\n defaultVariants: { size: \"md\" },\n});\n\nexport type KbdProps = React.ComponentProps<\"kbd\"> & {\n /**\n * sm · md.\n * @default \"md\"\n */\n size?: VariantProps<typeof kbdVariants>[\"size\"];\n};\n\nexport function Kbd({ size, className, ...props }: KbdProps) {\n return <kbd className={kbdVariants({ size, className })} {...props} />;\n}\n"],"mappings":"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","names":["tv","jsx","tv"]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/tag-input.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
Combobox
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LCZXS6AK.js";
|
|
5
5
|
import "./chunk-YH4LMWRU.js";
|
|
6
6
|
import "./chunk-ZIXB33X7.js";
|
|
7
7
|
import {
|
|
8
8
|
useControllable
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BRKMQ6JV.js";
|
|
10
10
|
import "./chunk-CPA5CF35.js";
|
|
11
11
|
|
|
12
12
|
// src/tag-input/tag-input.tsx
|
package/dist/video-player.js
CHANGED