@eifi1/ui-kit 0.42.0 → 0.43.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 +12 -6
- package/dist/admin/admin-area-labels.d.ts +4 -0
- package/dist/admin/admin-area-labels.js +3 -1
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/admin-overview.d.ts +25 -8
- package/dist/admin/admin-overview.js +26 -3
- package/dist/admin/admin-overview.js.map +1 -1
- package/dist/admin/admin-sections.d.ts +36 -5
- package/dist/admin/admin-sections.js +35 -18
- package/dist/admin/admin-sections.js.map +1 -1
- package/dist/billing/billing-banner.d.ts +8 -0
- package/dist/billing/billing-banner.js +25 -2
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/billing-labels.d.ts +4 -0
- package/dist/billing/billing-labels.js +1 -0
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/cancel-request.d.ts +53 -0
- package/dist/billing/cancel-request.js +21 -0
- package/dist/billing/cancel-request.js.map +1 -0
- package/dist/billing/provider-page.d.ts +40 -0
- package/dist/billing/provider-page.js +33 -0
- package/dist/billing/provider-page.js.map +1 -0
- package/dist/billing/subscription-actions.d.ts +32 -1
- package/dist/billing/subscription-actions.js +57 -4
- package/dist/billing/subscription-actions.js.map +1 -1
- package/dist/components/account-chips.d.ts +8 -1
- package/dist/components/account-chips.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-menu.d.ts +7 -4
- package/dist/feedback/feedback-menu.js +4 -2
- package/dist/feedback/feedback-menu.js.map +1 -1
- package/dist/hooks/use-search-param-state.js +4 -1
- package/dist/hooks/use-search-param-state.js.map +1 -1
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +36 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +3 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +51 -2
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +36 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +36 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +36 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +36 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +36 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +15 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/search/global-search.d.ts +3 -0
- package/dist/search/global-search.js +3 -1
- package/dist/search/global-search.js.map +1 -1
- package/dist/settings/settings-catalogue.d.ts +9 -1
- package/dist/settings/settings-catalogue.js +1 -1
- package/dist/settings/settings-catalogue.js.map +1 -1
- package/dist/shell/admin-menu.d.ts +26 -3
- package/dist/shell/admin-menu.js +8 -4
- package/dist/shell/admin-menu.js.map +1 -1
- package/dist/shell/dev-switchers.js +5 -0
- package/dist/shell/dev-switchers.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +6 -0
- package/dist/shell/top-bar-parts-labels.js +4 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell/tour-menu.d.ts +19 -10
- package/dist/shell/tour-menu.js +20 -6
- package/dist/shell/tour-menu.js.map +1 -1
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/tour/tour-catalogue-labels.d.ts +44 -0
- package/dist/tour/tour-catalogue-labels.js +32 -0
- package/dist/tour/tour-catalogue-labels.js.map +1 -0
- package/dist/tour/tour-catalogue.d.ts +68 -0
- package/dist/tour/tour-catalogue.js +189 -0
- package/dist/tour/tour-catalogue.js.map +1 -0
- package/dist/tour/tour-info.d.ts +112 -0
- package/dist/tour/tour-info.js +36 -0
- package/dist/tour/tour-info.js.map +1 -0
- package/dist/tour/use-tour-progress.d.ts +61 -0
- package/dist/tour/use-tour-progress.js +116 -0
- package/dist/tour/use-tour-progress.js.map +1 -0
- package/dist/tour/use-tour-url-sync.d.ts +58 -0
- package/dist/tour/use-tour-url-sync.js +110 -0
- package/dist/tour/use-tour-url-sync.js.map +1 -0
- package/dist/tour.d.ts +8 -0
- package/dist/tour.js +18 -0
- package/dist/tour.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/admin-area-labels.ts +7 -1
- package/src/admin/admin-overview.tsx +61 -10
- package/src/admin/admin-sections.tsx +109 -48
- package/src/billing/billing-banner.tsx +25 -2
- package/src/billing/billing-labels.ts +6 -0
- package/src/billing/cancel-request.ts +75 -0
- package/src/billing/provider-page.ts +75 -0
- package/src/billing/subscription-actions.tsx +97 -11
- package/src/components/account-chips.tsx +8 -1
- package/src/feedback/feedback-menu.tsx +8 -3
- package/src/hooks/use-search-param-state.ts +6 -1
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +36 -1
- package/src/i18n/kit-labels.tsx +3 -0
- package/src/i18n/locales/en.ts +13 -0
- package/src/i18n/locales/es.ts +36 -1
- package/src/i18n/locales/fr.ts +36 -1
- package/src/i18n/locales/hu.ts +36 -1
- package/src/i18n/locales/it.ts +36 -1
- package/src/i18n/locales/zh.ts +36 -1
- package/src/i18n/review.ts +14 -0
- package/src/index.ts +8 -0
- package/src/search/global-search.tsx +5 -0
- package/src/settings/settings-catalogue.ts +10 -2
- package/src/shell/admin-menu.tsx +34 -5
- package/src/shell/dev-switchers.tsx +8 -0
- package/src/shell/top-bar-parts-labels.ts +9 -0
- package/src/shell/tour-menu.tsx +50 -15
- package/src/shell.ts +3 -0
- package/src/tour/tour-catalogue-labels.ts +75 -0
- package/src/tour/tour-catalogue.tsx +316 -0
- package/src/tour/tour-info.ts +144 -0
- package/src/tour/use-tour-progress.ts +198 -0
- package/src/tour/use-tour-url-sync.ts +221 -0
- package/src/tour.ts +11 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/account-chips.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { formatDate, formatRelativeTime, toDate, EMPTY_FORMATTED_VALUE } from \"../lib/format\";\nimport type { DateInput, FormatDateStyle, FormatRelativeTimeOptions } from \"../lib/format\";\nimport { useKitDateFormatter, useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { toLocalIso } from \"../lib/dates\";\nimport { Chip } from \"./chip\";\nimport type { ChipLinkProps, ChipShape, ChipSize, ChipTone, ChipVariant } from \"./chip\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { DEFAULT_ACCOUNT_STATE_LABELS } from \"./account-chips-labels\";\n\n/**\n * The parts an admin roster repeats (0.18.0) — and only those.\n *\n * Kurvenschmiede (features/admin/users-panel.tsx, allowlist-panel.tsx) and keksdose\n * (features/admin/users-panel.tsx, the allowlist card in admin-page.tsx) each built the\n * same table: name over email, a role, whether the account is active, created, last\n * login, a row of actions, and an allowlist with a note. What they share is not the\n * table — the columns around it (keksdose's plan and privacy mode, Kurvenschmiede's\n * owned-work counts and its role SELECT), the actions and every rule about who may do\n * what are each app's own. What they share is the vocabulary: both spelled the same\n * square, uppercase status chip by hand (keksdose's `Badge` with its own tone map,\n * Kurvenschmiede's inline `<Chip size=\"xs\" shape=\"square\" caps>`), and both formatted\n * their two date columns by hand (`toLocaleDateString` in one, a \"3 days ago\" helper\n * in the other). So: {@link RoleChip}, {@link AccountStateChip} and\n * {@link dateColumn}, and the roster stays the app's `DataTable`.\n */\n\n/** The chip look both rosters use: a square, uppercase status tag. Each can be changed. */\ninterface AccountChipLook {\n /** Default `sm` — keksdose's roster; Kurvenschmiede's denser lists pass `xs`. */\n size?: ChipSize;\n /** Default `soft`. `dot` for a column of them that should not outshout the names. */\n variant?: ChipVariant;\n /** Default `square`: a chip in a table cell, beside square controls. */\n shape?: ChipShape;\n /** Default true — the status-badge type. */\n caps?: boolean;\n className?: string;\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/* ── RoleChip ──────────────────────────────────────────────────────────────── */\n\n/** How one role is drawn. `label` is the app's, translated. */\nexport interface RoleDefinition {\n label: string;\n tone?: ChipTone;\n /** A lucide component or an element — Kurvenschmiede marks its admins with a shield. */\n icon?: LucideIcon | ReactElement;\n /**\n * 0.42.0 (docs/admin-harmonization.md §2.1, settings §12.3): the role's tool — an\n * admin's `/admin`, a reviewer's `/translations`. {@link AccountRoleChip} is a link to\n * it; a role without one gets a plain chip. {@link RoleChip} ignores it: a roster's\n * chips name a role, they are not a way in.\n */\n href?: string;\n /**\n * 0.42.0 (keksdose build review): this role's look in {@link AccountRoleChip} — keksdose\n * draws only ADMIN `solid` and its REVIEWER and MEMBER `soft`. Default `solid`, the\n * header's look; the chip's own `variant` prop wins over it. {@link RoleChip} ignores\n * it: a roster's column keeps one look, set on the column.\n */\n variant?: ChipVariant;\n}\n\n/**\n * Every role an app has, keyed by the value its API sends. Annotate the app's table\n * with it (`const ROLES: RoleVocabulary<UserRole> = {…}`) and TypeScript asks for a\n * label when a role is added.\n */\nexport type RoleVocabulary<R extends string = string> = Readonly<Record<R, RoleDefinition>>;\n\nexport interface RoleChipProps<R extends string = string> extends AccountChipLook {\n /**\n * The role key as the API sends it. `null` / `undefined` renders nothing. Named\n * `value`, not `role`, because `role` on a JSX element is the ARIA role — the lint\n * rule and every reader would take it for one.\n */\n value: R | null | undefined;\n /** What each key is called and how it is toned. A key missing here is shown as the\n * key itself, in `neutral`: an unknown role is still a fact the admin should see. */\n roles: Readonly<Partial<Record<R, RoleDefinition>>>;\n}\n\n/**\n * A role as a chip: the API's key looked up in the app's {@link RoleVocabulary}.\n *\n * The vocabulary is a prop and not kit English, because the roles are the app's —\n * Kurvenschmiede has admin, member and customer, keksdose admin and user, kastlan its\n * own — and so are their names. Carries `data-role` with the key, for a test or a\n * style hook.\n */\nexport function RoleChip<R extends string = string>({\n value: role,\n roles,\n size = \"sm\",\n variant,\n shape = \"square\",\n caps = true,\n className,\n ...rest\n}: RoleChipProps<R>) {\n if (role === null || role === undefined || role === \"\") return null;\n const def = roles[role];\n return (\n <Chip\n {...rest}\n data-role={role}\n tone={def?.tone ?? \"neutral\"}\n icon={def?.icon}\n size={size}\n variant={variant}\n shape={shape}\n caps={caps}\n className={className}\n >\n {def?.label ?? role}\n </Chip>\n );\n}\n\n/* ── AccountRoleChip ───────────────────────────────────────────────────────── */\n\nexport interface AccountRoleChipProps<R extends string = string> extends RoleChipProps<R> {\n /**\n * Runs on the way to the role's tool — the account menu's `close`, so the panel does\n * not stay open over the page it just navigated to (keksdose F7). Only called on a\n * link chip.\n */\n onNavigate?: () => void;\n /** Your router's link for the role's `href`. Default: the `<UiKitProvider\n * linkComponent>`, then `<a>` — {@link Chip}'s `renderLink`. */\n renderLink?: (props: ChipLinkProps) => ReactElement;\n}\n\n/**\n * The role in the account menu's header (0.42.0, docs/admin-harmonization.md §2.1, §7):\n * {@link RoleChip} in keksdose's look — `solid`, `sm`, `square`, `caps`, the role's icon\n * and tone — made for `TopBarActionMenu`'s `header.extra`:\n *\n * extra: (close) => <AccountRoleChip value={me.role} roles={ROLES} onNavigate={close} />\n *\n * A role whose {@link RoleDefinition.href} is set is a shortcut to its tool: the chip is a\n * link (through `renderLink`, else the provider's link, as {@link Chip} draws links) and\n * calls `onNavigate` on the click. Any other role is a plain chip — every account shows\n * its role (§7, \"Always a badge\"). The words and tones are the app's vocabulary; the kit\n * adds none. `solid` stays the default, so a role the app gives no look still reads as\n * the header's tag; a role that should sit quieter (keksdose's MEMBER and REVIEWER) sets\n * {@link RoleDefinition.variant} `soft` in the vocabulary. Carries `data-role` with the key.\n */\nexport function AccountRoleChip<R extends string = string>({\n value: role,\n roles,\n onNavigate,\n renderLink,\n size = \"sm\",\n variant,\n shape = \"square\",\n caps = true,\n className,\n ...rest\n}: AccountRoleChipProps<R>) {\n if (role === null || role === undefined || role === \"\") return null;\n const def = roles[role];\n const look = {\n ...rest,\n \"data-role\": role,\n tone: def?.tone ?? \"neutral\",\n icon: def?.icon,\n size,\n // The prop, then the role's own look, then the header's `solid`.\n variant: variant ?? def?.variant ?? \"solid\",\n shape,\n caps,\n className,\n } as const;\n const label = def?.label ?? role;\n if (def?.href) {\n return (\n <Chip {...look} href={def.href} renderLink={renderLink} onClick={onNavigate ? () => onNavigate() : undefined}>\n {label}\n </Chip>\n );\n }\n return <Chip {...look}>{label}</Chip>;\n}\n\n/* ── AccountStateChip ──────────────────────────────────────────────────────── */\n\n/**\n * The states both rosters show, each as the app's own flag maps onto it:\n *\n * - `active` / `inactive` — the account may sign in, or an admin switched it off\n * (both apps' `is_active`).\n * - `invited` — on the allowlist and mailed, not registered yet (an allowlist row).\n * - `registered` — an allowlist row whose address has an account now (Kurvenschmiede).\n * - `unverified` — the email address is not confirmed (keksdose).\n * - `passwordChange` — must set a new password at the next sign-in (keksdose).\n * - `deletion` — 0.30.0: the user asked for the account to be deleted\n * (docs/user-admin-harmonization.md §2.1, §6.4). It is deactivated at once and erased\n * on a date (`after_days`: keksdose, Kurvenschmiede) or by an operator (kastlan). The\n * chip says the date when there is one ({@link AccountStateChipProps.date}), and\n * \"Deletion requested\" when there is none. It stands IN PLACE of `inactive`: an\n * account marked for deletion is always deactivated, and two chips would say it twice.\n *\n * An account can be several at once (inactive AND unverified): render one chip per\n * state, as keksdose's state column does.\n */\nexport type AccountState =\n | \"active\"\n | \"inactive\"\n | \"invited\"\n | \"registered\"\n | \"unverified\"\n | \"passwordChange\"\n | \"deletion\";\n\n/**\n * The `accountState` namespace. One word per state, plus the two forms of `deletion`.\n *\n * The 0.30 keys are OPTIONAL, like every key added to a namespace an app annotates\n * (`const L: AccountStateLabels = {…}`): a new required key would be a compile error in\n * each app on its next update. Left out, they fall back to the provider's, then English.\n */\nexport interface AccountStateLabels extends Record<Exclude<AccountState, \"deletion\">, string> {\n /** 0.30.0: an account marked for deletion with no date — kastlan's `operator` mode,\n * where an operator erases it (§6.4). \"Deletion requested\". */\n deletion?: string;\n /** 0.30.0: an account that will be erased on `date`, already formatted in the\n * reader's locale — the `after_days` mode. \"Deletion on 6 Nov 2026\". */\n deletionOn?: (date: string) => string;\n}\n\n// Defined in ./account-chips-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_ACCOUNT_STATE_LABELS };\n\n/** The default tone of each state: green for the normal case, red for switched off\n * (and for going, `deletion`), amber for something the user still has to do, blue for\n * an open invitation. */\nexport const ACCOUNT_STATE_TONES: Readonly<Record<AccountState, ChipTone>> = {\n active: \"success\",\n inactive: \"danger\",\n invited: \"info\",\n registered: \"neutral\",\n unverified: \"warning\",\n passwordChange: \"warning\",\n // Red like `inactive`, which it replaces: the account cannot sign in, and it is going.\n deletion: \"danger\",\n};\n\nexport interface AccountStateChipProps extends AccountChipLook {\n state: AccountState;\n /**\n * 0.30.0, `state=\"deletion\"` only: the day the account will be erased — the row's\n * `deletion_scheduled_at` (§3.2, §6.4). Formatted as a date in the reader's locale\n * (\"Deletion on 6 Nov 2026\"), through the provider's `formatDate` when the app set\n * one, as {@link DateMark} does. Missing or `null` — an operator erases it, so there\n * is no date — the chip says \"Deletion requested\". Ignored for every other state.\n */\n date?: DateInput;\n /** Over the provider's locale, for the `deletion` date. */\n locale?: string;\n /** Over {@link ACCOUNT_STATE_TONES} — Kurvenschmiede draws `inactive` neutral. */\n tone?: ChipTone;\n /** Over the label, for an app whose word for the state is its own (\"Deactivated\"). */\n children?: ReactNode;\n /** Prop > `<UiKitProvider labels={{ accountState }}>` > English. */\n labels?: LabelOverride<AccountStateLabels>;\n}\n\n/**\n * An account's state as a chip, in the kit's words and tones. Carries `data-state`\n * with the key.\n */\nexport function AccountStateChip({\n state,\n date,\n locale: localeProp,\n tone,\n children,\n labels: labelsProp,\n size = \"sm\",\n variant,\n shape = \"square\",\n caps = true,\n className,\n ...rest\n}: AccountStateChipProps) {\n // An `undefined` in the prop is skipped (the provider's merge does that), so an\n // optional value cannot blank a default.\n const labels = useKitLabels(\"accountState\", DEFAULT_ACCOUNT_STATE_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const when = state === \"deletion\" ? toDate(date ?? null) : null;\n let text: ReactNode;\n if (state !== \"deletion\") text = labels[state];\n else if (when) {\n // A date in a SENTENCE, so no weekday (keksdose's rule, KitDateFormatContext).\n const day =\n (fromProvider && fromProvider(toLocalIso(when), { unit: \"day\", source: \"dateMark\", locale, weekday: false })) ||\n formatDate(when, \"medium\", { locale });\n text = (labels.deletionOn ?? DEFAULT_ACCOUNT_STATE_LABELS.deletionOn)(day);\n } else text = labels.deletion ?? DEFAULT_ACCOUNT_STATE_LABELS.deletion;\n return (\n <Chip\n {...rest}\n data-state={state}\n data-date={when ? toLocalIso(when) : undefined}\n tone={tone ?? ACCOUNT_STATE_TONES[state]}\n size={size}\n variant={variant}\n shape={shape}\n caps={caps}\n className={className}\n >\n {children ?? text}\n </Chip>\n );\n}\n\n/* ── Date columns ──────────────────────────────────────────────────────────── */\n\nexport interface DateMarkProps {\n value: DateInput;\n /**\n * `\"date\"` (default): the date in `dateStyle` — \"Created\". `\"relative\"`: how long\n * ago — \"Last login\", where the question is \"recently?\" — with the full date and\n * time beside it (keksdose's last-seen column; see `absolute`).\n */\n display?: \"date\" | \"relative\";\n /**\n * Where a `\"relative\"` mark puts the exact date and time (0.32,\n * docs/text-size-harmonization.md §4: \"relative dates show the absolute one beside\n * them\", at every size — a touch screen never opens the tooltip it used to live in):\n * `\"below\"` (default), a muted second line under the words, so a table column keeps\n * its width; `\"inline\"`, after them on the same line, for a sentence or a wide cell.\n */\n absolute?: \"below\" | \"inline\";\n /** Default `medium`. Under `\"relative\"`, the style of the exact date beside it (default\n * `dateTime`) and of the date `relative.absoluteAfterDays` switches to. Given, it wins\n * over the provider's `formatDate`. */\n dateStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n /** `formatRelativeTime` options: `numeric`, `style`, `absoluteAfterDays`, `now`. */\n relative?: Omit<FormatRelativeTimeOptions, \"locale\" | \"empty\" | \"absoluteStyle\">;\n /** What a missing value reads as. Default \"—\"; a last-login column passes its\n * translated \"Never\". */\n empty?: ReactNode;\n /** Over the provider's locale. */\n locale?: string;\n className?: string;\n}\n\n/**\n * A date in a table cell: a `<time>` with a machine-readable `dateTime`, nowrap and\n * tabular figures, in the provider's locale — or `empty` when there is none.\n *\n * `display=\"date\"` without a `dateStyle` is written by `<UiKitProvider formatDate>` when\n * the app set one (keksdose K12: its `DateCell` exists to put every column's date in the\n * app's date-format preference, weekday and all — dev#546), called with the LOCAL\n * calendar day of the value as `\"YYYY-MM-DD\"` and `weekday: true`. Without one, or for\n * an empty answer, it is `Intl`'s `medium` date as before. The relative display is not\n * a calendar day and keeps `formatRelativeTime`.\n */\nexport function DateMark({\n value,\n display = \"date\",\n dateStyle,\n relative,\n absolute = \"below\",\n empty = EMPTY_FORMATTED_VALUE,\n locale: localeProp,\n className,\n}: DateMarkProps) {\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const date = toDate(value);\n const cls = cn(\"whitespace-nowrap tabular-nums\", className);\n if (!date) return <span className={cn(cls, \"text-muted\")}>{empty}</span>;\n if (display === \"date\") {\n const own =\n dateStyle === undefined && fromProvider\n ? fromProvider(toLocalIso(date), { unit: \"day\", source: \"dateMark\", locale, weekday: true })\n : \"\";\n return (\n <time dateTime={date.toISOString()} className={cls}>\n {own || formatDate(date, dateStyle ?? \"medium\", { locale })}\n </time>\n );\n }\n const text = formatRelativeTime(date, { ...relative, locale, absoluteStyle: dateStyle ?? \"medium\" });\n const exact = formatDate(date, dateStyle ?? \"dateTime\", { locale });\n // Past `absoluteAfterDays` the relative words already ARE the date; then the exact\n // line alone, rather than the same day twice.\n const sameDay = text === formatDate(date, dateStyle ?? \"medium\", { locale });\n const iso = date.toISOString();\n if (sameDay) {\n return (\n <time dateTime={iso} className={cls}>\n {exact}\n </time>\n );\n }\n // The exact date and time in the layout, never only in a tooltip (§4). Still no tab\n // stop: a roster of fifty rows would be fifty more of them.\n return (\n <time\n dateTime={iso}\n data-slot=\"date-mark\"\n className={cn(absolute === \"below\" ? \"inline-flex flex-col leading-tight\" : \"inline\", className)}\n >\n <span className=\"whitespace-nowrap tabular-nums\">{text}</span>\n {absolute === \"inline\" && \" · \"}\n <span className={cn(\"whitespace-nowrap tabular-nums text-muted\", absolute === \"below\" && \"text-xs\")}>\n {exact}\n </span>\n </time>\n );\n}\n\nexport interface DateColumnOptions<T> extends Omit<DateMarkProps, \"value\" | \"className\"> {\n key: string;\n header: ReactNode;\n /** The value of this row: an ISO string, epoch ms, a Date, or null for none. */\n value: (row: T) => DateInput;\n /** Sortable by the instant; rows with no date sort last either way. Default true. */\n sortable?: boolean;\n /** Default `desc` — a date column is sorted to find the newest. */\n firstSort?: \"asc\" | \"desc\";\n /** Add the table's from–to date filter over the column. Default false. */\n filter?: boolean;\n /** Further column settings (`mobileHidden`, `headClassName`, …), merged over. */\n column?: Partial<DataTableColumn<T>>;\n}\n\n/**\n * A {@link DataTableColumn} for a date: {@link DateMark} cells, sorted by the instant\n * (newest first on the first click), optionally the date filter. The roster's\n * \"Created\" is `dateColumn({ key, header, value })`; its \"Last login\" adds\n * `display: \"relative\", empty: t(\"never\")`.\n */\nexport function dateColumn<T>({\n key,\n header,\n value,\n sortable = true,\n firstSort = \"desc\",\n filter = false,\n column,\n ...mark\n}: DateColumnOptions<T>): DataTableColumn<T> {\n return {\n key,\n header,\n cell: (row) => <DateMark value={value(row)} {...mark} />,\n sortBy: sortable ? (row) => toDate(value(row))?.getTime() ?? null : undefined,\n firstSort: sortable ? firstSort : undefined,\n filter: filter\n ? {\n type: \"date\",\n // A string goes as it is: the filter reads a timestamp's local day itself.\n // A Date or epoch value goes as its LOCAL day, the one the cell shows —\n // `toISOString()` handed over the UTC day, and in Berlin a 00:30 row was\n // filtered under the day before (round 0.34 BUG-2).\n getValue: (row) => {\n const raw = value(row);\n if (typeof raw === \"string\") return raw;\n const date = toDate(raw);\n return date ? toLocalIso(date) : null;\n },\n }\n : undefined,\n ...column,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/account-chips.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { formatDate, formatRelativeTime, toDate, EMPTY_FORMATTED_VALUE } from \"../lib/format\";\nimport type { DateInput, FormatDateStyle, FormatRelativeTimeOptions } from \"../lib/format\";\nimport { useKitDateFormatter, useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { toLocalIso } from \"../lib/dates\";\nimport { Chip } from \"./chip\";\nimport type { ChipLinkProps, ChipShape, ChipSize, ChipTone, ChipVariant } from \"./chip\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { DEFAULT_ACCOUNT_STATE_LABELS } from \"./account-chips-labels\";\n\n/**\n * The parts an admin roster repeats (0.18.0) — and only those.\n *\n * Kurvenschmiede (features/admin/users-panel.tsx, allowlist-panel.tsx) and keksdose\n * (features/admin/users-panel.tsx, the allowlist card in admin-page.tsx) each built the\n * same table: name over email, a role, whether the account is active, created, last\n * login, a row of actions, and an allowlist with a note. What they share is not the\n * table — the columns around it (keksdose's plan and privacy mode, Kurvenschmiede's\n * owned-work counts and its role SELECT), the actions and every rule about who may do\n * what are each app's own. What they share is the vocabulary: both spelled the same\n * square, uppercase status chip by hand (keksdose's `Badge` with its own tone map,\n * Kurvenschmiede's inline `<Chip size=\"xs\" shape=\"square\" caps>`), and both formatted\n * their two date columns by hand (`toLocaleDateString` in one, a \"3 days ago\" helper\n * in the other). So: {@link RoleChip}, {@link AccountStateChip} and\n * {@link dateColumn}, and the roster stays the app's `DataTable`.\n */\n\n/** The chip look both rosters use: a square, uppercase status tag. Each can be changed. */\ninterface AccountChipLook {\n /** Default `sm` — keksdose's roster; Kurvenschmiede's denser lists pass `xs`. */\n size?: ChipSize;\n /** Default `soft`. `dot` for a column of them that should not outshout the names. */\n variant?: ChipVariant;\n /** Default `square`: a chip in a table cell, beside square controls. */\n shape?: ChipShape;\n /** Default true — the status-badge type. */\n caps?: boolean;\n className?: string;\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/* ── RoleChip ──────────────────────────────────────────────────────────────── */\n\n/** How one role is drawn. `label` is the app's, translated. */\nexport interface RoleDefinition {\n label: string;\n tone?: ChipTone;\n /**\n * A lucide component or an element — Kurvenschmiede marks its admins with a shield.\n * Unlike {@link href} and {@link variant}, it shows EVERYWHERE the role is drawn: in\n * {@link AccountRoleChip} and in {@link RoleChip} (the roster's column) alike. That is\n * intended (0.43, keksdose's question): the glyph is part of how the role reads, so the\n * shield on the header's chip is the shield in the users list. An app that wants the\n * glyph in one place only hands the other a second vocabulary without `icon`.\n */\n icon?: LucideIcon | ReactElement;\n /**\n * 0.42.0 (docs/admin-harmonization.md §2.1, settings §12.3): the role's tool — an\n * admin's `/admin`, a reviewer's `/translations`. {@link AccountRoleChip} is a link to\n * it; a role without one gets a plain chip. {@link RoleChip} ignores it: a roster's\n * chips name a role, they are not a way in.\n */\n href?: string;\n /**\n * 0.42.0 (keksdose build review): this role's look in {@link AccountRoleChip} — keksdose\n * draws only ADMIN `solid` and its REVIEWER and MEMBER `soft`. Default `solid`, the\n * header's look; the chip's own `variant` prop wins over it. {@link RoleChip} ignores\n * it: a roster's column keeps one look, set on the column.\n */\n variant?: ChipVariant;\n}\n\n/**\n * Every role an app has, keyed by the value its API sends. Annotate the app's table\n * with it (`const ROLES: RoleVocabulary<UserRole> = {…}`) and TypeScript asks for a\n * label when a role is added.\n */\nexport type RoleVocabulary<R extends string = string> = Readonly<Record<R, RoleDefinition>>;\n\nexport interface RoleChipProps<R extends string = string> extends AccountChipLook {\n /**\n * The role key as the API sends it. `null` / `undefined` renders nothing. Named\n * `value`, not `role`, because `role` on a JSX element is the ARIA role — the lint\n * rule and every reader would take it for one.\n */\n value: R | null | undefined;\n /** What each key is called and how it is toned. A key missing here is shown as the\n * key itself, in `neutral`: an unknown role is still a fact the admin should see. */\n roles: Readonly<Partial<Record<R, RoleDefinition>>>;\n}\n\n/**\n * A role as a chip: the API's key looked up in the app's {@link RoleVocabulary}.\n *\n * The vocabulary is a prop and not kit English, because the roles are the app's —\n * Kurvenschmiede has admin, member and customer, keksdose admin and user, kastlan its\n * own — and so are their names. Carries `data-role` with the key, for a test or a\n * style hook.\n */\nexport function RoleChip<R extends string = string>({\n value: role,\n roles,\n size = \"sm\",\n variant,\n shape = \"square\",\n caps = true,\n className,\n ...rest\n}: RoleChipProps<R>) {\n if (role === null || role === undefined || role === \"\") return null;\n const def = roles[role];\n return (\n <Chip\n {...rest}\n data-role={role}\n tone={def?.tone ?? \"neutral\"}\n icon={def?.icon}\n size={size}\n variant={variant}\n shape={shape}\n caps={caps}\n className={className}\n >\n {def?.label ?? role}\n </Chip>\n );\n}\n\n/* ── AccountRoleChip ───────────────────────────────────────────────────────── */\n\nexport interface AccountRoleChipProps<R extends string = string> extends RoleChipProps<R> {\n /**\n * Runs on the way to the role's tool — the account menu's `close`, so the panel does\n * not stay open over the page it just navigated to (keksdose F7). Only called on a\n * link chip.\n */\n onNavigate?: () => void;\n /** Your router's link for the role's `href`. Default: the `<UiKitProvider\n * linkComponent>`, then `<a>` — {@link Chip}'s `renderLink`. */\n renderLink?: (props: ChipLinkProps) => ReactElement;\n}\n\n/**\n * The role in the account menu's header (0.42.0, docs/admin-harmonization.md §2.1, §7):\n * {@link RoleChip} in keksdose's look — `solid`, `sm`, `square`, `caps`, the role's icon\n * and tone — made for `TopBarActionMenu`'s `header.extra`:\n *\n * extra: (close) => <AccountRoleChip value={me.role} roles={ROLES} onNavigate={close} />\n *\n * A role whose {@link RoleDefinition.href} is set is a shortcut to its tool: the chip is a\n * link (through `renderLink`, else the provider's link, as {@link Chip} draws links) and\n * calls `onNavigate` on the click. Any other role is a plain chip — every account shows\n * its role (§7, \"Always a badge\"). The words and tones are the app's vocabulary; the kit\n * adds none. `solid` stays the default, so a role the app gives no look still reads as\n * the header's tag; a role that should sit quieter (keksdose's MEMBER and REVIEWER) sets\n * {@link RoleDefinition.variant} `soft` in the vocabulary. Carries `data-role` with the key.\n */\nexport function AccountRoleChip<R extends string = string>({\n value: role,\n roles,\n onNavigate,\n renderLink,\n size = \"sm\",\n variant,\n shape = \"square\",\n caps = true,\n className,\n ...rest\n}: AccountRoleChipProps<R>) {\n if (role === null || role === undefined || role === \"\") return null;\n const def = roles[role];\n const look = {\n ...rest,\n \"data-role\": role,\n tone: def?.tone ?? \"neutral\",\n icon: def?.icon,\n size,\n // The prop, then the role's own look, then the header's `solid`.\n variant: variant ?? def?.variant ?? \"solid\",\n shape,\n caps,\n className,\n } as const;\n const label = def?.label ?? role;\n if (def?.href) {\n return (\n <Chip {...look} href={def.href} renderLink={renderLink} onClick={onNavigate ? () => onNavigate() : undefined}>\n {label}\n </Chip>\n );\n }\n return <Chip {...look}>{label}</Chip>;\n}\n\n/* ── AccountStateChip ──────────────────────────────────────────────────────── */\n\n/**\n * The states both rosters show, each as the app's own flag maps onto it:\n *\n * - `active` / `inactive` — the account may sign in, or an admin switched it off\n * (both apps' `is_active`).\n * - `invited` — on the allowlist and mailed, not registered yet (an allowlist row).\n * - `registered` — an allowlist row whose address has an account now (Kurvenschmiede).\n * - `unverified` — the email address is not confirmed (keksdose).\n * - `passwordChange` — must set a new password at the next sign-in (keksdose).\n * - `deletion` — 0.30.0: the user asked for the account to be deleted\n * (docs/user-admin-harmonization.md §2.1, §6.4). It is deactivated at once and erased\n * on a date (`after_days`: keksdose, Kurvenschmiede) or by an operator (kastlan). The\n * chip says the date when there is one ({@link AccountStateChipProps.date}), and\n * \"Deletion requested\" when there is none. It stands IN PLACE of `inactive`: an\n * account marked for deletion is always deactivated, and two chips would say it twice.\n *\n * An account can be several at once (inactive AND unverified): render one chip per\n * state, as keksdose's state column does.\n */\nexport type AccountState =\n | \"active\"\n | \"inactive\"\n | \"invited\"\n | \"registered\"\n | \"unverified\"\n | \"passwordChange\"\n | \"deletion\";\n\n/**\n * The `accountState` namespace. One word per state, plus the two forms of `deletion`.\n *\n * The 0.30 keys are OPTIONAL, like every key added to a namespace an app annotates\n * (`const L: AccountStateLabels = {…}`): a new required key would be a compile error in\n * each app on its next update. Left out, they fall back to the provider's, then English.\n */\nexport interface AccountStateLabels extends Record<Exclude<AccountState, \"deletion\">, string> {\n /** 0.30.0: an account marked for deletion with no date — kastlan's `operator` mode,\n * where an operator erases it (§6.4). \"Deletion requested\". */\n deletion?: string;\n /** 0.30.0: an account that will be erased on `date`, already formatted in the\n * reader's locale — the `after_days` mode. \"Deletion on 6 Nov 2026\". */\n deletionOn?: (date: string) => string;\n}\n\n// Defined in ./account-chips-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_ACCOUNT_STATE_LABELS };\n\n/** The default tone of each state: green for the normal case, red for switched off\n * (and for going, `deletion`), amber for something the user still has to do, blue for\n * an open invitation. */\nexport const ACCOUNT_STATE_TONES: Readonly<Record<AccountState, ChipTone>> = {\n active: \"success\",\n inactive: \"danger\",\n invited: \"info\",\n registered: \"neutral\",\n unverified: \"warning\",\n passwordChange: \"warning\",\n // Red like `inactive`, which it replaces: the account cannot sign in, and it is going.\n deletion: \"danger\",\n};\n\nexport interface AccountStateChipProps extends AccountChipLook {\n state: AccountState;\n /**\n * 0.30.0, `state=\"deletion\"` only: the day the account will be erased — the row's\n * `deletion_scheduled_at` (§3.2, §6.4). Formatted as a date in the reader's locale\n * (\"Deletion on 6 Nov 2026\"), through the provider's `formatDate` when the app set\n * one, as {@link DateMark} does. Missing or `null` — an operator erases it, so there\n * is no date — the chip says \"Deletion requested\". Ignored for every other state.\n */\n date?: DateInput;\n /** Over the provider's locale, for the `deletion` date. */\n locale?: string;\n /** Over {@link ACCOUNT_STATE_TONES} — Kurvenschmiede draws `inactive` neutral. */\n tone?: ChipTone;\n /** Over the label, for an app whose word for the state is its own (\"Deactivated\"). */\n children?: ReactNode;\n /** Prop > `<UiKitProvider labels={{ accountState }}>` > English. */\n labels?: LabelOverride<AccountStateLabels>;\n}\n\n/**\n * An account's state as a chip, in the kit's words and tones. Carries `data-state`\n * with the key.\n */\nexport function AccountStateChip({\n state,\n date,\n locale: localeProp,\n tone,\n children,\n labels: labelsProp,\n size = \"sm\",\n variant,\n shape = \"square\",\n caps = true,\n className,\n ...rest\n}: AccountStateChipProps) {\n // An `undefined` in the prop is skipped (the provider's merge does that), so an\n // optional value cannot blank a default.\n const labels = useKitLabels(\"accountState\", DEFAULT_ACCOUNT_STATE_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const when = state === \"deletion\" ? toDate(date ?? null) : null;\n let text: ReactNode;\n if (state !== \"deletion\") text = labels[state];\n else if (when) {\n // A date in a SENTENCE, so no weekday (keksdose's rule, KitDateFormatContext).\n const day =\n (fromProvider && fromProvider(toLocalIso(when), { unit: \"day\", source: \"dateMark\", locale, weekday: false })) ||\n formatDate(when, \"medium\", { locale });\n text = (labels.deletionOn ?? DEFAULT_ACCOUNT_STATE_LABELS.deletionOn)(day);\n } else text = labels.deletion ?? DEFAULT_ACCOUNT_STATE_LABELS.deletion;\n return (\n <Chip\n {...rest}\n data-state={state}\n data-date={when ? toLocalIso(when) : undefined}\n tone={tone ?? ACCOUNT_STATE_TONES[state]}\n size={size}\n variant={variant}\n shape={shape}\n caps={caps}\n className={className}\n >\n {children ?? text}\n </Chip>\n );\n}\n\n/* ── Date columns ──────────────────────────────────────────────────────────── */\n\nexport interface DateMarkProps {\n value: DateInput;\n /**\n * `\"date\"` (default): the date in `dateStyle` — \"Created\". `\"relative\"`: how long\n * ago — \"Last login\", where the question is \"recently?\" — with the full date and\n * time beside it (keksdose's last-seen column; see `absolute`).\n */\n display?: \"date\" | \"relative\";\n /**\n * Where a `\"relative\"` mark puts the exact date and time (0.32,\n * docs/text-size-harmonization.md §4: \"relative dates show the absolute one beside\n * them\", at every size — a touch screen never opens the tooltip it used to live in):\n * `\"below\"` (default), a muted second line under the words, so a table column keeps\n * its width; `\"inline\"`, after them on the same line, for a sentence or a wide cell.\n */\n absolute?: \"below\" | \"inline\";\n /** Default `medium`. Under `\"relative\"`, the style of the exact date beside it (default\n * `dateTime`) and of the date `relative.absoluteAfterDays` switches to. Given, it wins\n * over the provider's `formatDate`. */\n dateStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n /** `formatRelativeTime` options: `numeric`, `style`, `absoluteAfterDays`, `now`. */\n relative?: Omit<FormatRelativeTimeOptions, \"locale\" | \"empty\" | \"absoluteStyle\">;\n /** What a missing value reads as. Default \"—\"; a last-login column passes its\n * translated \"Never\". */\n empty?: ReactNode;\n /** Over the provider's locale. */\n locale?: string;\n className?: string;\n}\n\n/**\n * A date in a table cell: a `<time>` with a machine-readable `dateTime`, nowrap and\n * tabular figures, in the provider's locale — or `empty` when there is none.\n *\n * `display=\"date\"` without a `dateStyle` is written by `<UiKitProvider formatDate>` when\n * the app set one (keksdose K12: its `DateCell` exists to put every column's date in the\n * app's date-format preference, weekday and all — dev#546), called with the LOCAL\n * calendar day of the value as `\"YYYY-MM-DD\"` and `weekday: true`. Without one, or for\n * an empty answer, it is `Intl`'s `medium` date as before. The relative display is not\n * a calendar day and keeps `formatRelativeTime`.\n */\nexport function DateMark({\n value,\n display = \"date\",\n dateStyle,\n relative,\n absolute = \"below\",\n empty = EMPTY_FORMATTED_VALUE,\n locale: localeProp,\n className,\n}: DateMarkProps) {\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n const date = toDate(value);\n const cls = cn(\"whitespace-nowrap tabular-nums\", className);\n if (!date) return <span className={cn(cls, \"text-muted\")}>{empty}</span>;\n if (display === \"date\") {\n const own =\n dateStyle === undefined && fromProvider\n ? fromProvider(toLocalIso(date), { unit: \"day\", source: \"dateMark\", locale, weekday: true })\n : \"\";\n return (\n <time dateTime={date.toISOString()} className={cls}>\n {own || formatDate(date, dateStyle ?? \"medium\", { locale })}\n </time>\n );\n }\n const text = formatRelativeTime(date, { ...relative, locale, absoluteStyle: dateStyle ?? \"medium\" });\n const exact = formatDate(date, dateStyle ?? \"dateTime\", { locale });\n // Past `absoluteAfterDays` the relative words already ARE the date; then the exact\n // line alone, rather than the same day twice.\n const sameDay = text === formatDate(date, dateStyle ?? \"medium\", { locale });\n const iso = date.toISOString();\n if (sameDay) {\n return (\n <time dateTime={iso} className={cls}>\n {exact}\n </time>\n );\n }\n // The exact date and time in the layout, never only in a tooltip (§4). Still no tab\n // stop: a roster of fifty rows would be fifty more of them.\n return (\n <time\n dateTime={iso}\n data-slot=\"date-mark\"\n className={cn(absolute === \"below\" ? \"inline-flex flex-col leading-tight\" : \"inline\", className)}\n >\n <span className=\"whitespace-nowrap tabular-nums\">{text}</span>\n {absolute === \"inline\" && \" · \"}\n <span className={cn(\"whitespace-nowrap tabular-nums text-muted\", absolute === \"below\" && \"text-xs\")}>\n {exact}\n </span>\n </time>\n );\n}\n\nexport interface DateColumnOptions<T> extends Omit<DateMarkProps, \"value\" | \"className\"> {\n key: string;\n header: ReactNode;\n /** The value of this row: an ISO string, epoch ms, a Date, or null for none. */\n value: (row: T) => DateInput;\n /** Sortable by the instant; rows with no date sort last either way. Default true. */\n sortable?: boolean;\n /** Default `desc` — a date column is sorted to find the newest. */\n firstSort?: \"asc\" | \"desc\";\n /** Add the table's from–to date filter over the column. Default false. */\n filter?: boolean;\n /** Further column settings (`mobileHidden`, `headClassName`, …), merged over. */\n column?: Partial<DataTableColumn<T>>;\n}\n\n/**\n * A {@link DataTableColumn} for a date: {@link DateMark} cells, sorted by the instant\n * (newest first on the first click), optionally the date filter. The roster's\n * \"Created\" is `dateColumn({ key, header, value })`; its \"Last login\" adds\n * `display: \"relative\", empty: t(\"never\")`.\n */\nexport function dateColumn<T>({\n key,\n header,\n value,\n sortable = true,\n firstSort = \"desc\",\n filter = false,\n column,\n ...mark\n}: DateColumnOptions<T>): DataTableColumn<T> {\n return {\n key,\n header,\n cell: (row) => <DateMark value={value(row)} {...mark} />,\n sortBy: sortable ? (row) => toDate(value(row))?.getTime() ?? null : undefined,\n firstSort: sortable ? firstSort : undefined,\n filter: filter\n ? {\n type: \"date\",\n // A string goes as it is: the filter reads a timestamp's local day itself.\n // A Date or epoch value goes as its LOCAL day, the one the cell shows —\n // `toISOString()` handed over the UTC day, and in Berlin a 00:30 row was\n // filtered under the day before (round 0.34 BUG-2).\n getValue: (row) => {\n const raw = value(row);\n if (typeof raw === \"string\") return raw;\n const date = toDate(raw);\n return date ? toLocalIso(date) : null;\n },\n }\n : undefined,\n ...column,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/eslint/index.js
CHANGED
|
@@ -47,7 +47,7 @@ const roleUtilities = {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
|
-
const VERSION = true ? "0.
|
|
50
|
+
const VERSION = true ? "0.43.0" : "0.0.0-source";
|
|
51
51
|
const recommended = {
|
|
52
52
|
name: "@eifi1/ui-kit/recommended",
|
|
53
53
|
// Not the tests, as check:tokens skips the kit's: a test names the old class to assert it
|
|
@@ -62,6 +62,9 @@ export interface FeedbackMenuProps {
|
|
|
62
62
|
/** Which edge of the trigger the panel lines up with, `start` or `end` in the reading
|
|
63
63
|
* direction (see `HoverMenu`, whose type this is). Default `end`. */
|
|
64
64
|
align?: HoverMenuProps["align"];
|
|
65
|
+
/** 0.43: `data-*` attributes go on the trigger — a tour's anchor
|
|
66
|
+
* (`data-tour="feedback-menu"`), a test hook. */
|
|
67
|
+
[key: `data-${string}`]: string | number | boolean | undefined;
|
|
65
68
|
}
|
|
66
69
|
/**
|
|
67
70
|
* The top-bar feedback menu, the same in every app (0.27.0, docs/feedback-harmonization.md
|
|
@@ -91,8 +94,8 @@ export interface FeedbackMenuProps {
|
|
|
91
94
|
* to the top like one with none of what makes it actionable. The server refuses it with a
|
|
92
95
|
* 422 anyway (§3.4).
|
|
93
96
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
+
* Since 0.43 its `data-*` attributes go on the trigger (keksdose's tour anchor
|
|
98
|
+
* `data-tour="feedback-menu"` needs no wrapping `<span>` any more). Needs a router, as
|
|
99
|
+
* every `TopBarActionMenu` with link rows does.
|
|
97
100
|
*/
|
|
98
|
-
export declare function FeedbackMenu({ onFile, isAdmin, extraEntries, myFeedbackHref, inboxHref, publicHref, iconBadge, labels: labelsProp, align, }: FeedbackMenuProps): import("react").JSX.Element;
|
|
101
|
+
export declare function FeedbackMenu({ onFile, isAdmin, extraEntries, myFeedbackHref, inboxHref, publicHref, iconBadge, labels: labelsProp, align, ...attributes }: FeedbackMenuProps): import("react").JSX.Element;
|
|
@@ -16,7 +16,8 @@ function FeedbackMenu({
|
|
|
16
16
|
publicHref,
|
|
17
17
|
iconBadge,
|
|
18
18
|
labels: labelsProp,
|
|
19
|
-
align
|
|
19
|
+
align,
|
|
20
|
+
...attributes
|
|
20
21
|
}) {
|
|
21
22
|
const labels = useKitLabels("feedbackMenu", DEFAULT_FEEDBACK_MENU_LABELS, labelsProp);
|
|
22
23
|
const categoryLabels = useFeedbackCategoryLabels();
|
|
@@ -65,7 +66,8 @@ function FeedbackMenu({
|
|
|
65
66
|
iconBadge,
|
|
66
67
|
"aria-label": labels.trigger,
|
|
67
68
|
entries,
|
|
68
|
-
align
|
|
69
|
+
align,
|
|
70
|
+
triggerAttributes: Object.fromEntries(Object.entries(attributes).filter(([k]) => k.startsWith("data-")))
|
|
69
71
|
}
|
|
70
72
|
);
|
|
71
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/feedback/feedback-menu.tsx"],"sourcesContent":["import { Inbox, MessageSquare, User, Users } from \"lucide-react\";\nimport type { UserAvatarBadge } from \"../components/user-avatar\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"../shell/topbar-action-menu\";\nimport type { HoverMenuProps } from \"../components/hover-menu\";\n// The vocabulary module, not the inbox: this menu is in every app's top bar, the inbox\n// is a page (MOD-1).\nimport { FEEDBACK_CATEGORY_META, type FeedbackCategory } from \"./feedback-vocabulary\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FEEDBACK_PICKABLE_CATEGORIES } from \"./feedback-record\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"./feedback-menu-labels\";\n\n/**\n * `feedbackMenu` — the top-bar feedback menu's own words (0.27.0). The four category rows\n * are `feedbackCategory`'s, shared with the badges and the dialog's picker. de-CH canon\n * (keksdose `de-CH.json`, §10.6): `trigger` \"Feedback senden\", `discussion`\n * \"Feedback-Diskussion\", `myFeedback` \"Mein Feedback\", `inbox` \"Feedback-Eingang\".\n */\nexport interface FeedbackMenuLabels {\n /** The trigger's accessible name, and the menu's. keksdose `feedback.send`. */\n trigger: string;\n /** The link to the viewer's own reports — everyone's, an admin's too since 0.40\n * (§10.2 decision 3). keksdose `feedback.my_title`. */\n myFeedback: string;\n /** 0.40: the link to the public list, {@link FeedbackMenuProps.publicHref} (§10.2\n * decision 3; \"All feedback\" before). de-CH \"Feedback-Diskussion\". */\n discussion: string;\n /** 0.40: the admin's link to the triage list, {@link FeedbackMenuProps.inboxHref}\n * (\"View feedback\" before). de-CH \"Feedback-Eingang\". */\n inbox: string;\n}\n\n// Defined in ./feedback-menu-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_FEEDBACK_MENU_LABELS };\n\nexport interface FeedbackMenuProps {\n /** A category row was chosen — open the submit dialog on it (`useFeedbackSubmit`'s\n * `open`). Never called with `CRASH`: there is no row for it. */\n onFile: (category: FeedbackCategory) => void;\n /** Adds the **Feedback inbox** row → {@link inboxHref} under \"My feedback\" (0.40, §10.2\n * decision 11; until 0.39 it replaced it). The admin check is the app's (kastlan's is\n * company-scoped). Default `false`. */\n isAdmin?: boolean;\n /**\n * Rows of the app's own, between the divider and the list link — keksdose's\n * `Sparkles` Help assistant → `/assistant` and `MessagesSquare` Support chat →\n * `/support` with its unread chip. Any {@link TopBarMenuEntry}; keep their `key`s clear\n * of the menu's own (`BUG`, `IDEA`, `QUESTION`, `OTHER`, `feedback-divider`,\n * `all-feedback`, `my-feedback`, `view-feedback`).\n */\n extraEntries?: TopBarMenuEntry[];\n /** The viewer's own reports, linked for everyone (an admin too since 0.40). Default\n * `\"/my-feedback\"` (§2.3). */\n myFeedbackHref?: string;\n /** The admin inbox, linked for an admin. Default `\"/feedback\"` (§2.3). */\n inboxHref?: string;\n /**\n * The public list (0.38, docs/feedback-harmonization.md §9.2 decision 21): when passed,\n * a **Feedback discussion** row (`Users`, key `all-feedback`; \"All feedback\" until 0.40)\n * first of the list links, for everyone — the admin too, who reads what the others read\n * there. keksdose: `\"/all-feedback\"`. Leave it out where the app has no\n * public parts (kastlan) or the viewer may not see them (a Kurvenschmiede customer, a\n * demo — §9.2 decisions 7, 8).\n */\n publicHref?: string;\n /** A dot on the trigger — keksdose's unread support replies (`tone: \"danger\"`). Its\n * `label` joins the trigger's name (\"Send feedback 2 unread\"). The words are the app's. */\n iconBadge?: UserAvatarBadge | null;\n /** Prop > `<UiKitProvider labels={{ feedbackMenu }}>` > English. */\n labels?: LabelOverride<FeedbackMenuLabels>;\n /** Which edge of the trigger the panel lines up with, `start` or `end` in the reading\n * direction (see `HoverMenu`, whose type this is). Default `end`. */\n align?: HoverMenuProps[\"align\"];\n}\n\n/**\n * The top-bar feedback menu, the same in every app (0.27.0, docs/feedback-harmonization.md\n * §4.1) — keksdose `frontend/src/app/top-bar.tsx:156` on the kit's `TopBarActionMenu`:\n *\n * 1. a `MessageSquare` trigger named \"Send feedback\", with an optional `iconBadge`;\n * 2. Bug · Idea · Question · Other (`Bug`, `Lightbulb`, `HelpCircle`, `MoreHorizontal` —\n * the category badges' own glyphs), each calling `onFile(category)`;\n * 3. a divider, always;\n * 4. the app's `extraEntries`;\n * 5. the list links (0.40, §10.2 decision 11), in this order: **Feedback discussion**\n * (`Users`, key `all-feedback`) → `publicHref`, when passed; **My feedback** (`User`,\n * key `my-feedback`) → `/my-feedback`, for everyone, admins included; **Feedback\n * inbox** (`Inbox`, key `view-feedback`) → `/feedback`, for an admin. An admin with\n * public parts sees three, a member two; without them an admin two and anyone else\n * one. Until 0.39 an admin had the inbox in place of their own list (feedback #331);\n * Marcel's kk #419 gave them both.\n *\n * WHY A PRESET. Three apps had drawn three menus on the same primitive and they disagreed:\n * kastlan headed it \"New submission\", Kurvenschmiede \"Send feedback\" with a single link\n * that changed its words by role, keksdose had no heading and one link by role. keksdose\n * is the source (§2.4, §7.11): no heading, the list links by role — and the kit draws\n * it, so the row order, the icons and the words cannot drift again. Where the menu sits in the bar stays each app's.\n *\n * `CRASH` has no row and never will: crash reports are filed by the error boundary with\n * the stack and a fingerprint (keksdose feedback #160), and a hand-filed \"crash\" would sort\n * to the top like one with none of what makes it actionable. The server refuses it with a\n * 422 anyway (§3.4).\n *\n *
|
|
1
|
+
{"version":3,"sources":["../../src/feedback/feedback-menu.tsx"],"sourcesContent":["import { Inbox, MessageSquare, User, Users } from \"lucide-react\";\nimport type { UserAvatarBadge } from \"../components/user-avatar\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"../shell/topbar-action-menu\";\nimport type { HoverMenuProps } from \"../components/hover-menu\";\n// The vocabulary module, not the inbox: this menu is in every app's top bar, the inbox\n// is a page (MOD-1).\nimport { FEEDBACK_CATEGORY_META, type FeedbackCategory } from \"./feedback-vocabulary\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FEEDBACK_PICKABLE_CATEGORIES } from \"./feedback-record\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"./feedback-menu-labels\";\n\n/**\n * `feedbackMenu` — the top-bar feedback menu's own words (0.27.0). The four category rows\n * are `feedbackCategory`'s, shared with the badges and the dialog's picker. de-CH canon\n * (keksdose `de-CH.json`, §10.6): `trigger` \"Feedback senden\", `discussion`\n * \"Feedback-Diskussion\", `myFeedback` \"Mein Feedback\", `inbox` \"Feedback-Eingang\".\n */\nexport interface FeedbackMenuLabels {\n /** The trigger's accessible name, and the menu's. keksdose `feedback.send`. */\n trigger: string;\n /** The link to the viewer's own reports — everyone's, an admin's too since 0.40\n * (§10.2 decision 3). keksdose `feedback.my_title`. */\n myFeedback: string;\n /** 0.40: the link to the public list, {@link FeedbackMenuProps.publicHref} (§10.2\n * decision 3; \"All feedback\" before). de-CH \"Feedback-Diskussion\". */\n discussion: string;\n /** 0.40: the admin's link to the triage list, {@link FeedbackMenuProps.inboxHref}\n * (\"View feedback\" before). de-CH \"Feedback-Eingang\". */\n inbox: string;\n}\n\n// Defined in ./feedback-menu-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_FEEDBACK_MENU_LABELS };\n\nexport interface FeedbackMenuProps {\n /** A category row was chosen — open the submit dialog on it (`useFeedbackSubmit`'s\n * `open`). Never called with `CRASH`: there is no row for it. */\n onFile: (category: FeedbackCategory) => void;\n /** Adds the **Feedback inbox** row → {@link inboxHref} under \"My feedback\" (0.40, §10.2\n * decision 11; until 0.39 it replaced it). The admin check is the app's (kastlan's is\n * company-scoped). Default `false`. */\n isAdmin?: boolean;\n /**\n * Rows of the app's own, between the divider and the list link — keksdose's\n * `Sparkles` Help assistant → `/assistant` and `MessagesSquare` Support chat →\n * `/support` with its unread chip. Any {@link TopBarMenuEntry}; keep their `key`s clear\n * of the menu's own (`BUG`, `IDEA`, `QUESTION`, `OTHER`, `feedback-divider`,\n * `all-feedback`, `my-feedback`, `view-feedback`).\n */\n extraEntries?: TopBarMenuEntry[];\n /** The viewer's own reports, linked for everyone (an admin too since 0.40). Default\n * `\"/my-feedback\"` (§2.3). */\n myFeedbackHref?: string;\n /** The admin inbox, linked for an admin. Default `\"/feedback\"` (§2.3). */\n inboxHref?: string;\n /**\n * The public list (0.38, docs/feedback-harmonization.md §9.2 decision 21): when passed,\n * a **Feedback discussion** row (`Users`, key `all-feedback`; \"All feedback\" until 0.40)\n * first of the list links, for everyone — the admin too, who reads what the others read\n * there. keksdose: `\"/all-feedback\"`. Leave it out where the app has no\n * public parts (kastlan) or the viewer may not see them (a Kurvenschmiede customer, a\n * demo — §9.2 decisions 7, 8).\n */\n publicHref?: string;\n /** A dot on the trigger — keksdose's unread support replies (`tone: \"danger\"`). Its\n * `label` joins the trigger's name (\"Send feedback 2 unread\"). The words are the app's. */\n iconBadge?: UserAvatarBadge | null;\n /** Prop > `<UiKitProvider labels={{ feedbackMenu }}>` > English. */\n labels?: LabelOverride<FeedbackMenuLabels>;\n /** Which edge of the trigger the panel lines up with, `start` or `end` in the reading\n * direction (see `HoverMenu`, whose type this is). Default `end`. */\n align?: HoverMenuProps[\"align\"];\n /** 0.43: `data-*` attributes go on the trigger — a tour's anchor\n * (`data-tour=\"feedback-menu\"`), a test hook. */\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/**\n * The top-bar feedback menu, the same in every app (0.27.0, docs/feedback-harmonization.md\n * §4.1) — keksdose `frontend/src/app/top-bar.tsx:156` on the kit's `TopBarActionMenu`:\n *\n * 1. a `MessageSquare` trigger named \"Send feedback\", with an optional `iconBadge`;\n * 2. Bug · Idea · Question · Other (`Bug`, `Lightbulb`, `HelpCircle`, `MoreHorizontal` —\n * the category badges' own glyphs), each calling `onFile(category)`;\n * 3. a divider, always;\n * 4. the app's `extraEntries`;\n * 5. the list links (0.40, §10.2 decision 11), in this order: **Feedback discussion**\n * (`Users`, key `all-feedback`) → `publicHref`, when passed; **My feedback** (`User`,\n * key `my-feedback`) → `/my-feedback`, for everyone, admins included; **Feedback\n * inbox** (`Inbox`, key `view-feedback`) → `/feedback`, for an admin. An admin with\n * public parts sees three, a member two; without them an admin two and anyone else\n * one. Until 0.39 an admin had the inbox in place of their own list (feedback #331);\n * Marcel's kk #419 gave them both.\n *\n * WHY A PRESET. Three apps had drawn three menus on the same primitive and they disagreed:\n * kastlan headed it \"New submission\", Kurvenschmiede \"Send feedback\" with a single link\n * that changed its words by role, keksdose had no heading and one link by role. keksdose\n * is the source (§2.4, §7.11): no heading, the list links by role — and the kit draws\n * it, so the row order, the icons and the words cannot drift again. Where the menu sits in the bar stays each app's.\n *\n * `CRASH` has no row and never will: crash reports are filed by the error boundary with\n * the stack and a fingerprint (keksdose feedback #160), and a hand-filed \"crash\" would sort\n * to the top like one with none of what makes it actionable. The server refuses it with a\n * 422 anyway (§3.4).\n *\n * Since 0.43 its `data-*` attributes go on the trigger (keksdose's tour anchor\n * `data-tour=\"feedback-menu\"` needs no wrapping `<span>` any more). Needs a router, as\n * every `TopBarActionMenu` with link rows does.\n */\nexport function FeedbackMenu({\n onFile,\n isAdmin = false,\n extraEntries,\n myFeedbackHref = \"/my-feedback\",\n inboxHref = \"/feedback\",\n publicHref,\n iconBadge,\n labels: labelsProp,\n align,\n ...attributes\n}: FeedbackMenuProps) {\n const labels = useKitLabels(\"feedbackMenu\", DEFAULT_FEEDBACK_MENU_LABELS, labelsProp);\n const categoryLabels = useFeedbackCategoryLabels();\n const entries: TopBarMenuEntry[] = [\n ...FEEDBACK_PICKABLE_CATEGORIES.map((category): TopBarMenuEntry => {\n const Icon = FEEDBACK_CATEGORY_META[category].icon;\n return {\n key: category,\n icon: <Icon className=\"size-4\" />,\n label: categoryLabels[category],\n onSelect: () => onFile(category),\n };\n }),\n { kind: \"divider\", key: \"feedback-divider\" },\n ...(extraEntries ?? []),\n ...(publicHref\n ? [\n {\n kind: \"link\",\n key: \"all-feedback\",\n to: publicHref,\n icon: <Users className=\"size-4\" />,\n label: labels.discussion,\n } satisfies TopBarMenuEntry,\n ]\n : []),\n {\n kind: \"link\",\n key: \"my-feedback\",\n to: myFeedbackHref,\n icon: <User className=\"size-4\" />,\n label: labels.myFeedback,\n },\n ...(isAdmin\n ? [\n {\n kind: \"link\",\n key: \"view-feedback\",\n to: inboxHref,\n icon: <Inbox className=\"size-4\" />,\n label: labels.inbox,\n } satisfies TopBarMenuEntry,\n ]\n : []),\n ];\n return (\n <TopBarActionMenu\n icon={<MessageSquare className=\"size-5\" />}\n iconBadge={iconBadge}\n aria-label={labels.trigger}\n entries={entries}\n align={align}\n triggerAttributes={Object.fromEntries(Object.entries(attributes).filter(([k]) => k.startsWith(\"data-\")))}\n />\n );\n}\n"],"mappings":";AAiIc;AAjId,SAAS,OAAO,eAAe,MAAM,aAAa;AAElD,SAAS,oBAAwC;AACjD,SAAS,wBAA8C;AAIvD,SAAS,8BAAqD;AAC9D,SAAS,iCAAiC;AAC1C,SAAS,oCAAoC;AAC7C,SAAS,oCAAoC;AAoGtC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,SAAS,aAAa,gBAAgB,8BAA8B,UAAU;AACpF,QAAM,iBAAiB,0BAA0B;AACjD,QAAM,UAA6B;AAAA,IACjC,GAAG,6BAA6B,IAAI,CAAC,aAA8B;AACjE,YAAM,OAAO,uBAAuB,QAAQ,EAAE;AAC9C,aAAO;AAAA,QACL,KAAK;AAAA,QACL,MAAM,oBAAC,QAAK,WAAU,UAAS;AAAA,QAC/B,OAAO,eAAe,QAAQ;AAAA,QAC9B,UAAU,MAAM,OAAO,QAAQ;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,IACD,EAAE,MAAM,WAAW,KAAK,mBAAmB;AAAA,IAC3C,GAAI,gBAAgB,CAAC;AAAA,IACrB,GAAI,aACA;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,KAAK;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,oBAAC,SAAM,WAAU,UAAS;AAAA,QAChC,OAAO,OAAO;AAAA,MAChB;AAAA,IACF,IACA,CAAC;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,oBAAC,QAAK,WAAU,UAAS;AAAA,MAC/B,OAAO,OAAO;AAAA,IAChB;AAAA,IACA,GAAI,UACA;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,KAAK;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,oBAAC,SAAM,WAAU,UAAS;AAAA,QAChC,OAAO,OAAO;AAAA,MAChB;AAAA,IACF,IACA,CAAC;AAAA,EACP;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,oBAAC,iBAAc,WAAU,UAAS;AAAA,MACxC;AAAA,MACA,cAAY,OAAO;AAAA,MACnB;AAAA,MACA;AAAA,MACA,mBAAmB,OAAO,YAAY,OAAO,QAAQ,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW,OAAO,CAAC,CAAC;AAAA;AAAA,EACzG;AAEJ;","names":[]}
|
|
@@ -73,7 +73,10 @@ function writeQuery(location, navigate, dataRouter, change) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
queued.lastPushed = push;
|
|
76
|
-
void navigate(
|
|
76
|
+
void navigate(
|
|
77
|
+
{ search: `?${search}`, hash: location.hash },
|
|
78
|
+
push ? void 0 : { replace: true, state: carriedState(queued.state) }
|
|
79
|
+
);
|
|
77
80
|
}
|
|
78
81
|
function useQueryWriter() {
|
|
79
82
|
const location = useLocation();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/use-search-param-state.ts"],"sourcesContent":["import { createContext, useContext, useMemo, type Context } from \"react\";\nimport * as ReactRouter from \"react-router\";\nimport { useLocation, useNavigate, useSearchParams, type Location, type NavigateFunction } from \"react-router\";\n\ntype Updater<T> = T | ((prev: T) => T);\n\nexport interface SearchParamStateOptions<T> {\n /**\n * The value of a param that IS present. Return `undefined` (or throw) for a value\n * that does not parse — a hand-edited or stale link — and the default is used.\n * Default: the raw string.\n */\n parse?: (raw: string) => T | undefined;\n /**\n * The param for a value, or `null` to remove it. Default: `String(value)`, and\n * `null` for `null` / `undefined`. A value that serialises the same as the default\n * removes the param too, so the default is always the clean URL.\n */\n serialize?: (value: T) => string | null;\n /** Shorthand for `history: \"replace\"`. */\n replace?: boolean;\n /**\n * What a change does to the browser history:\n * - `\"push\"` (default): a new entry, so Back undoes the change.\n * - `\"replace\"`: the current entry is rewritten — a tab, a sort, a view toggle,\n * which are not steps anyone wants Back to walk through.\n * - `\"replace-on-clear\"`: push when a value is set, replace when it is cleared (the\n * \"open a form, close it\" pattern).\n *\n * Several writes in one tick (one handler) add at most ONE entry — see\n * {@link useSearchParamState}.\n */\n history?: \"push\" | \"replace\" | \"replace-on-clear\";\n}\n\nconst defaultSerialize = (value: unknown): string | null =>\n value === null || value === undefined ? null : String(value);\n\n/** The route state of the current entry, carried over a `replace` so a query rewrite\n * does not drop what a navigation put there (a dialog's opener mark, below). */\nfunction carriedState(state: unknown): Record<string, unknown> | undefined {\n return state && typeof state === \"object\" ? (state as Record<string, unknown>) : undefined;\n}\n\n/* ── One param: read and write ───────────────────────────────────────────── */\n\n/** One param's rules — the single hook's arguments, or one field of the multi-key hook. */\ninterface ParamRule<T> {\n key: string;\n defaultValue: T;\n parse?: (raw: string) => T | undefined;\n serialize: (value: T) => string | null;\n history: \"push\" | \"replace\" | \"replace-on-clear\";\n}\n\nfunction paramRule<T>(key: string, defaultValue: T, options: SearchParamStateOptions<T>): ParamRule<T> {\n return {\n key,\n defaultValue,\n parse: options.parse,\n serialize: options.serialize ?? (defaultSerialize as (value: T) => string | null),\n history: options.history ?? (options.replace ? \"replace\" : \"push\"),\n };\n}\n\n/** An absent or unparseable param reads as the default. */\nfunction readParam<T>(params: URLSearchParams, rule: ParamRule<T>): T {\n const raw = params.get(rule.key);\n if (raw === null) return rule.defaultValue;\n if (!rule.parse) return raw as unknown as T;\n try {\n const parsed = rule.parse(raw);\n return parsed === undefined ? rule.defaultValue : parsed;\n } catch {\n return rule.defaultValue;\n }\n}\n\n/**\n * Writes `value` into `params` — the default removes the param. Returns `null` when\n * the param already says that (a write that changes nothing has no say in the history),\n * otherwise whether this change asks for an entry of its own.\n */\nfunction putParam<T>(params: URLSearchParams, rule: ParamRule<T>, value: T): boolean | null {\n const serialized = rule.serialize(value);\n const next = serialized === rule.serialize(rule.defaultValue) ? null : serialized;\n const current = params.getAll(rule.key);\n if (next === null ? current.length === 0 : current.length === 1 && current[0] === next) return null;\n if (next === null) params.delete(rule.key);\n else params.set(rule.key, next);\n return rule.history === \"push\" || (rule.history === \"replace-on-clear\" && next !== null);\n}\n\n/* ── Writes that compose ─────────────────────────────────────────────────── */\n\n/**\n * The query as the writes made against ONE rendered location leave it — shared by every\n * hook on that router, so a write builds on the one before it instead of on the render.\n *\n * Why this exists (keksdose live #378): react-router's `setSearchParams(updater)` hands\n * its updater `new URLSearchParams(searchParams)` — the params of the RENDER the setter\n * came from, in react-router 7 and 8 alike — not the previous call's result. keksdose\n * wired TranslationReviewPanel's `onFilterChange` to four of these setters (status,\n * source, ns, q); each started from the rendered URL, so the last wrote the old values\n * of the other three back and no filter select had any effect.\n *\n * Keyed by react-router's location object, which is one object per committed location\n * per router: every hook under a router reads the same one, a second router (another\n * test) has its own, and when react-router renders the next location this record is\n * unreachable — nothing queued outlives the navigation it belongs to. Until then a\n * later write builds on it even a tick on (a transition or a data router's loaders still\n * pending), which is what keeps two quick clicks from clobbering each other either. The\n * one cost: a navigation a data router's blocker stops leaves its writes here, and the\n * page's next write carries them again — the blocker asks again.\n */\ninterface QueuedQuery {\n /** The query with every write against this location so far. */\n params: URLSearchParams;\n /** The route state of the entry those writes now sit on, carried over a replace. */\n state: unknown;\n /** A write in the current tick pushed; the tick's later writes rewrite that entry. */\n pushedThisTick: boolean;\n /** The last navigation sent was a push. */\n lastPushed: boolean;\n}\n\nconst queuedByLocation = new WeakMap<Location, QueuedQuery>();\n\n/**\n * The data router's own state, read for ONE question: is a navigation still loading?\n * A data router whose loaders run commits a navigation only when they finish, and a\n * second `navigate` before then cancels the first — so a push followed by a replace\n * in the same tick would land as a replace alone. `UNSAFE_DataRouterContext` is in\n * react-router 6.4 through 8; read off the namespace with a stand-in, a release\n * without it costs only that refinement, not the import.\n */\ntype DataRouterProbe = { router?: { state?: { navigation?: { state?: string } } } } | null;\nconst NO_DATA_ROUTER = createContext<DataRouterProbe>(null);\nconst DataRouterContext: Context<DataRouterProbe> = readDataRouterContext();\n\n/**\n * A static member read (so a bundler still tree-shakes the namespace), inside a `try`:\n * a test's `vi.mock(\"react-router\", () => ({ … }))` without the original module throws\n * on ANY export it does not define, and it threw here at import (keksdose, 0.25.0) —\n * the hook then runs without the refinement, as on a router without the context.\n */\nfunction readDataRouterContext(): Context<DataRouterProbe> {\n try {\n return (\n (ReactRouter as unknown as { UNSAFE_DataRouterContext?: Context<DataRouterProbe> }).UNSAFE_DataRouterContext ??\n NO_DATA_ROUTER\n );\n } catch {\n return NO_DATA_ROUTER;\n }\n}\n\n/**\n * One write: `change` edits the queued query in place and says whether a param it\n * changed asks for its own entry. The navigation goes out at once — a lone write\n * behaves exactly as before, and a test can assert right after `act` — carrying the\n * whole queued query, so it includes every write before it.\n *\n * History, per tick (the writes one handler makes before a microtask runs):\n * - the FIRST write that asks to push pushes; every later write in the tick REPLACES\n * that entry, so a handler that sets four params adds one entry, and Back undoes\n * all four at once — they were one change;\n * - a write that asks to replace, before any push, rewrites the entry it found;\n * - so mixed modes combine as \"push if any write asked to push\", which keeps both\n * promises: a push-mode param's change is undone by Back, and a replace-mode param\n * never adds an entry of its own (it rides on the one the push made). A replace\n * write ahead of a push stays on the entry it rewrote, as it would one tick apart.\n * - a write that changes nothing has no say, and sends nothing.\n */\nfunction writeQuery(\n location: Location,\n navigate: NavigateFunction,\n dataRouter: DataRouterProbe,\n change: (params: URLSearchParams) => boolean,\n): void {\n let queued = queuedByLocation.get(location);\n if (!queued) {\n queued = {\n params: new URLSearchParams(location.search),\n state: location.state,\n pushedThisTick: false,\n lastPushed: false,\n };\n queuedByLocation.set(location, queued);\n }\n const before = queued.params.toString();\n const wantsPush = change(queued.params);\n const search = queued.params.toString();\n if (search === before) return;\n\n // A data router still loading our push has not written it to the history yet: a\n // replace now would cancel it. Push again — the new navigation takes its place.\n const loading = (dataRouter?.router?.state?.navigation?.state ?? \"idle\") !== \"idle\";\n const push = (queued.lastPushed && loading) || (wantsPush && !queued.pushedThisTick);\n if (push) {\n queued.state = null;\n if (!queued.pushedThisTick) {\n queued.pushedThisTick = true;\n const record = queued;\n queueMicrotask(() => {\n record.pushedThisTick = false;\n });\n }\n }\n queued.lastPushed = push;\n void navigate(`?${search}`, push ? undefined : { replace: true, state: carriedState(queued.state) });\n}\n\n/** The rendered query, and a write that composes with every other one on the router. */\nfunction useQueryWriter(): [URLSearchParams, (change: (params: URLSearchParams) => boolean) => void] {\n const location = useLocation();\n const navigate = useNavigate();\n const dataRouter = useContext(DataRouterContext);\n const params = useMemo(() => new URLSearchParams(location.search), [location.search]);\n return [params, (change) => writeQuery(location, navigate, dataRouter, change)];\n}\n\n/* ── The hooks ───────────────────────────────────────────────────────────── */\n\n/**\n * One URL search param as React state: `const [view, setView] = useSearchParamState(\"view\", \"list\")`.\n *\n * Replaces the hand-rolled `setParams((prev) => { const p = new URLSearchParams(prev);\n * …set/delete…; return p })` blocks that keksdose had in a dozen pages and kastlan ported\n * as its own copy of this hook (shared/hooks/use-search-param-state.ts, both apps). The\n * kit's version takes the DEFAULT as an argument rather than inside `parse`, so the\n * two rules that follow from it cannot be forgotten at a call site:\n *\n * * an absent or unparseable param reads as the default, and\n * * setting the default removes the param — the default view is the clean URL.\n *\n * Every other param in the query string is left as it is.\n *\n * **Setters compose** (0.25, keksdose live #378): setters called in a row — of one hook,\n * or of several hooks under the same router — each build on the write before them, so\n * `setStatus(\"open\"); setNs(\"legal\")` in one handler leaves both in the URL. react-\n * router's own `setSearchParams(updater)` does not: it hands every updater the params of\n * the render, so the last call used to write the others' old values back. The tick's\n * writes add at most one history entry; see `history`. When the params belong\n * together, {@link useSearchParamsState} writes them with one setter. A functional\n * update receives the value as the writes before it left it.\n *\n * Requires a react-router Router.\n */\nexport function useSearchParamState<T = string>(\n key: string,\n defaultValue: T,\n options: SearchParamStateOptions<T> = {},\n): [T, (next: Updater<T>) => void] {\n const [params, write] = useQueryWriter();\n const rule = paramRule(key, defaultValue, options);\n const value = readParam(params, rule);\n\n const setValue = (next: Updater<T>) =>\n write((query) => {\n // Resolved against the queued query, not the render: a second update in the same\n // handler sees the first one's result, as React's own functional updates do.\n const resolved = typeof next === \"function\" ? (next as (prev: T) => T)(readParam(query, rule)) : next;\n return putParam(query, rule, resolved) === true;\n });\n\n return [value, setValue];\n}\n\n/** One field of {@link useSearchParamsState}: its default, and the single hook's options. */\nexport interface SearchParamField<T> extends SearchParamStateOptions<T> {\n /** The value when the param is absent or does not parse; writing it removes the param. */\n default: T;\n /** The param's name in the URL. Default: the field's name (`namespace: { param: \"ns\" }`). */\n param?: string;\n}\n\n/** The fields of {@link useSearchParamsState}, one per key of the value object. */\nexport type SearchParamFields<V> = { [K in keyof V]: SearchParamField<V[K]> };\n\n/** A partial value (fields left out — or `undefined` — stay as they are), or a function\n * of the current value returning one. */\nexport type SearchParamsUpdate<V> = Partial<V> | ((prev: V) => Partial<V>);\n\n/**\n * Several URL search params as ONE state object, with one setter that writes them in\n * one navigation:\n *\n * const [filter, setFilter] = useSearchParamsState({\n * status: { default: \"all\" as ReviewStatus | \"all\", parse: toStatus },\n * namespace: { param: \"ns\", default: \"all\" },\n * query: { param: \"q\", default: \"\", history: \"replace\" },\n * });\n * <TranslationReviewPanel filter={filter} onFilterChange={setFilter} />\n *\n * keksdose live #378: a filter bar reports all its filters on every change, and four\n * single-param setters in a row clobbered each other (see {@link useSearchParamState},\n * whose setters now compose too). This is the shape that never could: one setter, one\n * write, every field's rules in one place.\n *\n * Each field takes what the single hook takes — `parse`, `serialize`, `history` /\n * `replace` — and its `default` (the clean URL, as there) and optional `param` name.\n * The setter takes a partial object or a function of the current one; fields it leaves\n * out, or sets to `undefined`, stay as they are, and keys that are not fields are\n * ignored, so a component's whole filter object can be handed over as it is. Its\n * history is the single hook's rule over the fields that change: one entry, pushed if\n * any of them asks to push, else the current entry rewritten. Setting what the URL\n * already says navigates nowhere.\n *\n * The value object keeps its identity while its params do not change, so it can sit in\n * a dependency array. Requires a react-router Router.\n */\nexport function useSearchParamsState<V extends object>(\n fields: SearchParamFields<V>,\n): [V, (next: SearchParamsUpdate<V>) => void] {\n const [params, write] = useQueryWriter();\n const rules = (Object.keys(fields) as Array<keyof V & string>).map((name) => {\n const field = fields[name];\n return [name, paramRule(field.param ?? name, field.default, field)] as const;\n });\n const readAll = (query: URLSearchParams): V =>\n Object.fromEntries(rules.map(([name, rule]) => [name, readParam(query, rule)])) as V;\n\n // The fields' own params, not the whole query: another param changing (a tab, a\n // dialog) leaves this object as it was. `fields` is a fresh literal every render;\n // what it reads is the same while the params and the field names are.\n const ownParams = JSON.stringify(rules.map(([name, rule]) => [name, params.getAll(rule.key)]));\n // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed on what the value is read from; see above\n const values = useMemo(() => readAll(params), [ownParams]);\n\n const setValues = (next: SearchParamsUpdate<V>) =>\n write((query) => {\n const patch = typeof next === \"function\" ? next(readAll(query)) : next;\n let push = false;\n for (const [name, rule] of rules) {\n const value = patch[name];\n if (value === undefined) continue;\n if (putParam(query, rule as ParamRule<unknown>, value) === true) push = true;\n }\n return push;\n });\n\n return [values, setValues];\n}\n\n/**\n * The active tab in `?tab=` (or `key`), with the default tab as the clean URL, for the\n * kit's `Tabs`:\n *\n * const [tab, setTab] = useTabParam(\"overview\", { tabs: [\"overview\", \"history\"] });\n * <Tabs tabs={…} active={tab} onChange={setTab} />\n *\n * Switching tabs REPLACES the history entry — a tab is a view of this page, not a\n * place Back should step through. `tabs`, when given, turns a param naming no tab (a\n * renamed tab, an old link) into the default rather than into a blank panel.\n * kastlan's use-tab-param.ts, with the validation it did not have.\n */\nexport function useTabParam<T extends string>(\n defaultTab: T,\n options: { key?: string; tabs?: readonly T[] } = {},\n): [T, (tab: T) => void] {\n const { key = \"tab\", tabs } = options;\n return useSearchParamState<T>(key, defaultTab, {\n parse: (raw) => (!tabs || (tabs as readonly string[]).includes(raw) ? (raw as T) : undefined),\n history: \"replace\",\n });\n}\n\n/** Where a dialog's open state lives: a param's presence (`\"edit\"` → `?edit=1`), or one\n * value of a shared param (`{ key: \"dialog\", value: \"create-lease\" }`). */\nexport type DialogParam = string | { key: string; value: string };\n\n/** The route-state mark an opener leaves on the entry it pushes, so the close knows it\n * may go BACK over it rather than add a second entry. */\nconst OPENED_BY_KIT = \"__uiKitDialogParam\";\n\n/**\n * A dialog's open state in the URL, for `Modal`'s `urlParam` and anything else that\n * opens one: `const [open, setOpen] = useDialogParam({ key: \"dialog\", value: \"create\" })`.\n *\n * * **Opening pushes** an entry, so the platform Back gesture closes the dialog — by\n * leaving the entry — and Forward opens it again, like any other step.\n * * **Closing goes back** over that entry when this hook pushed it, so a closed dialog\n * leaves no dead entry behind. A dialog that arrived OPEN (a deep link, a reload, a\n * plain `<Link>` to it) has nothing of ours to go back over, and its close removes the\n * param in place instead — Back from there leaves the page, as it should.\n *\n * That is why it is not `useSearchParamState`: going back is the only close that\n * leaves the history as it was before the dialog, and only the opener knows whether it\n * may.\n */\nexport function useDialogParam(param: DialogParam): [boolean, (open: boolean) => void] {\n const { key, value } = typeof param === \"string\" ? { key: param, value: \"1\" } : param;\n const [params, setParams] = useSearchParams();\n const location = useLocation();\n const navigate = useNavigate();\n const open = typeof param === \"string\" ? params.has(key) : params.get(key) === value;\n const mark = `${key}=${value}`;\n\n const setOpen = (next: boolean) => {\n if (next === open) return;\n const state = carriedState(location.state);\n if (next) {\n setParams(\n (prev) => {\n const p = new URLSearchParams(prev);\n p.set(key, value);\n return p;\n },\n { state: { ...state, [OPENED_BY_KIT]: mark } },\n );\n return;\n }\n if (state?.[OPENED_BY_KIT] === mark) {\n void navigate(-1);\n return;\n }\n setParams(\n (prev) => {\n const p = new URLSearchParams(prev);\n p.delete(key);\n return p;\n },\n { replace: true, state },\n );\n };\n\n return [open, setOpen];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/use-search-param-state.ts"],"sourcesContent":["import { createContext, useContext, useMemo, type Context } from \"react\";\nimport * as ReactRouter from \"react-router\";\nimport { useLocation, useNavigate, useSearchParams, type Location, type NavigateFunction } from \"react-router\";\n\ntype Updater<T> = T | ((prev: T) => T);\n\nexport interface SearchParamStateOptions<T> {\n /**\n * The value of a param that IS present. Return `undefined` (or throw) for a value\n * that does not parse — a hand-edited or stale link — and the default is used.\n * Default: the raw string.\n */\n parse?: (raw: string) => T | undefined;\n /**\n * The param for a value, or `null` to remove it. Default: `String(value)`, and\n * `null` for `null` / `undefined`. A value that serialises the same as the default\n * removes the param too, so the default is always the clean URL.\n */\n serialize?: (value: T) => string | null;\n /** Shorthand for `history: \"replace\"`. */\n replace?: boolean;\n /**\n * What a change does to the browser history:\n * - `\"push\"` (default): a new entry, so Back undoes the change.\n * - `\"replace\"`: the current entry is rewritten — a tab, a sort, a view toggle,\n * which are not steps anyone wants Back to walk through.\n * - `\"replace-on-clear\"`: push when a value is set, replace when it is cleared (the\n * \"open a form, close it\" pattern).\n *\n * Several writes in one tick (one handler) add at most ONE entry — see\n * {@link useSearchParamState}.\n */\n history?: \"push\" | \"replace\" | \"replace-on-clear\";\n}\n\nconst defaultSerialize = (value: unknown): string | null =>\n value === null || value === undefined ? null : String(value);\n\n/** The route state of the current entry, carried over a `replace` so a query rewrite\n * does not drop what a navigation put there (a dialog's opener mark, below). */\nfunction carriedState(state: unknown): Record<string, unknown> | undefined {\n return state && typeof state === \"object\" ? (state as Record<string, unknown>) : undefined;\n}\n\n/* ── One param: read and write ───────────────────────────────────────────── */\n\n/** One param's rules — the single hook's arguments, or one field of the multi-key hook. */\ninterface ParamRule<T> {\n key: string;\n defaultValue: T;\n parse?: (raw: string) => T | undefined;\n serialize: (value: T) => string | null;\n history: \"push\" | \"replace\" | \"replace-on-clear\";\n}\n\nfunction paramRule<T>(key: string, defaultValue: T, options: SearchParamStateOptions<T>): ParamRule<T> {\n return {\n key,\n defaultValue,\n parse: options.parse,\n serialize: options.serialize ?? (defaultSerialize as (value: T) => string | null),\n history: options.history ?? (options.replace ? \"replace\" : \"push\"),\n };\n}\n\n/** An absent or unparseable param reads as the default. */\nfunction readParam<T>(params: URLSearchParams, rule: ParamRule<T>): T {\n const raw = params.get(rule.key);\n if (raw === null) return rule.defaultValue;\n if (!rule.parse) return raw as unknown as T;\n try {\n const parsed = rule.parse(raw);\n return parsed === undefined ? rule.defaultValue : parsed;\n } catch {\n return rule.defaultValue;\n }\n}\n\n/**\n * Writes `value` into `params` — the default removes the param. Returns `null` when\n * the param already says that (a write that changes nothing has no say in the history),\n * otherwise whether this change asks for an entry of its own.\n */\nfunction putParam<T>(params: URLSearchParams, rule: ParamRule<T>, value: T): boolean | null {\n const serialized = rule.serialize(value);\n const next = serialized === rule.serialize(rule.defaultValue) ? null : serialized;\n const current = params.getAll(rule.key);\n if (next === null ? current.length === 0 : current.length === 1 && current[0] === next) return null;\n if (next === null) params.delete(rule.key);\n else params.set(rule.key, next);\n return rule.history === \"push\" || (rule.history === \"replace-on-clear\" && next !== null);\n}\n\n/* ── Writes that compose ─────────────────────────────────────────────────── */\n\n/**\n * The query as the writes made against ONE rendered location leave it — shared by every\n * hook on that router, so a write builds on the one before it instead of on the render.\n *\n * Why this exists (keksdose live #378): react-router's `setSearchParams(updater)` hands\n * its updater `new URLSearchParams(searchParams)` — the params of the RENDER the setter\n * came from, in react-router 7 and 8 alike — not the previous call's result. keksdose\n * wired TranslationReviewPanel's `onFilterChange` to four of these setters (status,\n * source, ns, q); each started from the rendered URL, so the last wrote the old values\n * of the other three back and no filter select had any effect.\n *\n * Keyed by react-router's location object, which is one object per committed location\n * per router: every hook under a router reads the same one, a second router (another\n * test) has its own, and when react-router renders the next location this record is\n * unreachable — nothing queued outlives the navigation it belongs to. Until then a\n * later write builds on it even a tick on (a transition or a data router's loaders still\n * pending), which is what keeps two quick clicks from clobbering each other either. The\n * one cost: a navigation a data router's blocker stops leaves its writes here, and the\n * page's next write carries them again — the blocker asks again.\n */\ninterface QueuedQuery {\n /** The query with every write against this location so far. */\n params: URLSearchParams;\n /** The route state of the entry those writes now sit on, carried over a replace. */\n state: unknown;\n /** A write in the current tick pushed; the tick's later writes rewrite that entry. */\n pushedThisTick: boolean;\n /** The last navigation sent was a push. */\n lastPushed: boolean;\n}\n\nconst queuedByLocation = new WeakMap<Location, QueuedQuery>();\n\n/**\n * The data router's own state, read for ONE question: is a navigation still loading?\n * A data router whose loaders run commits a navigation only when they finish, and a\n * second `navigate` before then cancels the first — so a push followed by a replace\n * in the same tick would land as a replace alone. `UNSAFE_DataRouterContext` is in\n * react-router 6.4 through 8; read off the namespace with a stand-in, a release\n * without it costs only that refinement, not the import.\n */\ntype DataRouterProbe = { router?: { state?: { navigation?: { state?: string } } } } | null;\nconst NO_DATA_ROUTER = createContext<DataRouterProbe>(null);\nconst DataRouterContext: Context<DataRouterProbe> = readDataRouterContext();\n\n/**\n * A static member read (so a bundler still tree-shakes the namespace), inside a `try`:\n * a test's `vi.mock(\"react-router\", () => ({ … }))` without the original module throws\n * on ANY export it does not define, and it threw here at import (keksdose, 0.25.0) —\n * the hook then runs without the refinement, as on a router without the context.\n */\nfunction readDataRouterContext(): Context<DataRouterProbe> {\n try {\n return (\n (ReactRouter as unknown as { UNSAFE_DataRouterContext?: Context<DataRouterProbe> }).UNSAFE_DataRouterContext ??\n NO_DATA_ROUTER\n );\n } catch {\n return NO_DATA_ROUTER;\n }\n}\n\n/**\n * One write: `change` edits the queued query in place and says whether a param it\n * changed asks for its own entry. The navigation goes out at once — a lone write\n * behaves exactly as before, and a test can assert right after `act` — carrying the\n * whole queued query, so it includes every write before it.\n *\n * History, per tick (the writes one handler makes before a microtask runs):\n * - the FIRST write that asks to push pushes; every later write in the tick REPLACES\n * that entry, so a handler that sets four params adds one entry, and Back undoes\n * all four at once — they were one change;\n * - a write that asks to replace, before any push, rewrites the entry it found;\n * - so mixed modes combine as \"push if any write asked to push\", which keeps both\n * promises: a push-mode param's change is undone by Back, and a replace-mode param\n * never adds an entry of its own (it rides on the one the push made). A replace\n * write ahead of a push stays on the entry it rewrote, as it would one tick apart.\n * - a write that changes nothing has no say, and sends nothing.\n */\nfunction writeQuery(\n location: Location,\n navigate: NavigateFunction,\n dataRouter: DataRouterProbe,\n change: (params: URLSearchParams) => boolean,\n): void {\n let queued = queuedByLocation.get(location);\n if (!queued) {\n queued = {\n params: new URLSearchParams(location.search),\n state: location.state,\n pushedThisTick: false,\n lastPushed: false,\n };\n queuedByLocation.set(location, queued);\n }\n const before = queued.params.toString();\n const wantsPush = change(queued.params);\n const search = queued.params.toString();\n if (search === before) return;\n\n // A data router still loading our push has not written it to the history yet: a\n // replace now would cancel it. Push again — the new navigation takes its place.\n const loading = (dataRouter?.router?.state?.navigation?.state ?? \"idle\") !== \"idle\";\n const push = (queued.lastPushed && loading) || (wantsPush && !queued.pushedThisTick);\n if (push) {\n queued.state = null;\n if (!queued.pushedThisTick) {\n queued.pushedThisTick = true;\n const record = queued;\n queueMicrotask(() => {\n record.pushedThisTick = false;\n });\n }\n }\n queued.lastPushed = push;\n // The hash is spelled out: `navigate(\"?…\")` drops it (keksdose dev#495; build review\n // 0.43, keksdose N2 — the tours page's filters).\n void navigate(\n { search: `?${search}`, hash: location.hash },\n push ? undefined : { replace: true, state: carriedState(queued.state) },\n );\n}\n\n/** The rendered query, and a write that composes with every other one on the router. */\nfunction useQueryWriter(): [URLSearchParams, (change: (params: URLSearchParams) => boolean) => void] {\n const location = useLocation();\n const navigate = useNavigate();\n const dataRouter = useContext(DataRouterContext);\n const params = useMemo(() => new URLSearchParams(location.search), [location.search]);\n return [params, (change) => writeQuery(location, navigate, dataRouter, change)];\n}\n\n/* ── The hooks ───────────────────────────────────────────────────────────── */\n\n/**\n * One URL search param as React state: `const [view, setView] = useSearchParamState(\"view\", \"list\")`.\n *\n * Replaces the hand-rolled `setParams((prev) => { const p = new URLSearchParams(prev);\n * …set/delete…; return p })` blocks that keksdose had in a dozen pages and kastlan ported\n * as its own copy of this hook (shared/hooks/use-search-param-state.ts, both apps). The\n * kit's version takes the DEFAULT as an argument rather than inside `parse`, so the\n * two rules that follow from it cannot be forgotten at a call site:\n *\n * * an absent or unparseable param reads as the default, and\n * * setting the default removes the param — the default view is the clean URL.\n *\n * Every other param in the query string is left as it is.\n *\n * **Setters compose** (0.25, keksdose live #378): setters called in a row — of one hook,\n * or of several hooks under the same router — each build on the write before them, so\n * `setStatus(\"open\"); setNs(\"legal\")` in one handler leaves both in the URL. react-\n * router's own `setSearchParams(updater)` does not: it hands every updater the params of\n * the render, so the last call used to write the others' old values back. The tick's\n * writes add at most one history entry; see `history`. When the params belong\n * together, {@link useSearchParamsState} writes them with one setter. A functional\n * update receives the value as the writes before it left it.\n *\n * Requires a react-router Router.\n */\nexport function useSearchParamState<T = string>(\n key: string,\n defaultValue: T,\n options: SearchParamStateOptions<T> = {},\n): [T, (next: Updater<T>) => void] {\n const [params, write] = useQueryWriter();\n const rule = paramRule(key, defaultValue, options);\n const value = readParam(params, rule);\n\n const setValue = (next: Updater<T>) =>\n write((query) => {\n // Resolved against the queued query, not the render: a second update in the same\n // handler sees the first one's result, as React's own functional updates do.\n const resolved = typeof next === \"function\" ? (next as (prev: T) => T)(readParam(query, rule)) : next;\n return putParam(query, rule, resolved) === true;\n });\n\n return [value, setValue];\n}\n\n/** One field of {@link useSearchParamsState}: its default, and the single hook's options. */\nexport interface SearchParamField<T> extends SearchParamStateOptions<T> {\n /** The value when the param is absent or does not parse; writing it removes the param. */\n default: T;\n /** The param's name in the URL. Default: the field's name (`namespace: { param: \"ns\" }`). */\n param?: string;\n}\n\n/** The fields of {@link useSearchParamsState}, one per key of the value object. */\nexport type SearchParamFields<V> = { [K in keyof V]: SearchParamField<V[K]> };\n\n/** A partial value (fields left out — or `undefined` — stay as they are), or a function\n * of the current value returning one. */\nexport type SearchParamsUpdate<V> = Partial<V> | ((prev: V) => Partial<V>);\n\n/**\n * Several URL search params as ONE state object, with one setter that writes them in\n * one navigation:\n *\n * const [filter, setFilter] = useSearchParamsState({\n * status: { default: \"all\" as ReviewStatus | \"all\", parse: toStatus },\n * namespace: { param: \"ns\", default: \"all\" },\n * query: { param: \"q\", default: \"\", history: \"replace\" },\n * });\n * <TranslationReviewPanel filter={filter} onFilterChange={setFilter} />\n *\n * keksdose live #378: a filter bar reports all its filters on every change, and four\n * single-param setters in a row clobbered each other (see {@link useSearchParamState},\n * whose setters now compose too). This is the shape that never could: one setter, one\n * write, every field's rules in one place.\n *\n * Each field takes what the single hook takes — `parse`, `serialize`, `history` /\n * `replace` — and its `default` (the clean URL, as there) and optional `param` name.\n * The setter takes a partial object or a function of the current one; fields it leaves\n * out, or sets to `undefined`, stay as they are, and keys that are not fields are\n * ignored, so a component's whole filter object can be handed over as it is. Its\n * history is the single hook's rule over the fields that change: one entry, pushed if\n * any of them asks to push, else the current entry rewritten. Setting what the URL\n * already says navigates nowhere.\n *\n * The value object keeps its identity while its params do not change, so it can sit in\n * a dependency array. Requires a react-router Router.\n */\nexport function useSearchParamsState<V extends object>(\n fields: SearchParamFields<V>,\n): [V, (next: SearchParamsUpdate<V>) => void] {\n const [params, write] = useQueryWriter();\n const rules = (Object.keys(fields) as Array<keyof V & string>).map((name) => {\n const field = fields[name];\n return [name, paramRule(field.param ?? name, field.default, field)] as const;\n });\n const readAll = (query: URLSearchParams): V =>\n Object.fromEntries(rules.map(([name, rule]) => [name, readParam(query, rule)])) as V;\n\n // The fields' own params, not the whole query: another param changing (a tab, a\n // dialog) leaves this object as it was. `fields` is a fresh literal every render;\n // what it reads is the same while the params and the field names are.\n const ownParams = JSON.stringify(rules.map(([name, rule]) => [name, params.getAll(rule.key)]));\n // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed on what the value is read from; see above\n const values = useMemo(() => readAll(params), [ownParams]);\n\n const setValues = (next: SearchParamsUpdate<V>) =>\n write((query) => {\n const patch = typeof next === \"function\" ? next(readAll(query)) : next;\n let push = false;\n for (const [name, rule] of rules) {\n const value = patch[name];\n if (value === undefined) continue;\n if (putParam(query, rule as ParamRule<unknown>, value) === true) push = true;\n }\n return push;\n });\n\n return [values, setValues];\n}\n\n/**\n * The active tab in `?tab=` (or `key`), with the default tab as the clean URL, for the\n * kit's `Tabs`:\n *\n * const [tab, setTab] = useTabParam(\"overview\", { tabs: [\"overview\", \"history\"] });\n * <Tabs tabs={…} active={tab} onChange={setTab} />\n *\n * Switching tabs REPLACES the history entry — a tab is a view of this page, not a\n * place Back should step through. `tabs`, when given, turns a param naming no tab (a\n * renamed tab, an old link) into the default rather than into a blank panel.\n * kastlan's use-tab-param.ts, with the validation it did not have.\n */\nexport function useTabParam<T extends string>(\n defaultTab: T,\n options: { key?: string; tabs?: readonly T[] } = {},\n): [T, (tab: T) => void] {\n const { key = \"tab\", tabs } = options;\n return useSearchParamState<T>(key, defaultTab, {\n parse: (raw) => (!tabs || (tabs as readonly string[]).includes(raw) ? (raw as T) : undefined),\n history: \"replace\",\n });\n}\n\n/** Where a dialog's open state lives: a param's presence (`\"edit\"` → `?edit=1`), or one\n * value of a shared param (`{ key: \"dialog\", value: \"create-lease\" }`). */\nexport type DialogParam = string | { key: string; value: string };\n\n/** The route-state mark an opener leaves on the entry it pushes, so the close knows it\n * may go BACK over it rather than add a second entry. */\nconst OPENED_BY_KIT = \"__uiKitDialogParam\";\n\n/**\n * A dialog's open state in the URL, for `Modal`'s `urlParam` and anything else that\n * opens one: `const [open, setOpen] = useDialogParam({ key: \"dialog\", value: \"create\" })`.\n *\n * * **Opening pushes** an entry, so the platform Back gesture closes the dialog — by\n * leaving the entry — and Forward opens it again, like any other step.\n * * **Closing goes back** over that entry when this hook pushed it, so a closed dialog\n * leaves no dead entry behind. A dialog that arrived OPEN (a deep link, a reload, a\n * plain `<Link>` to it) has nothing of ours to go back over, and its close removes the\n * param in place instead — Back from there leaves the page, as it should.\n *\n * That is why it is not `useSearchParamState`: going back is the only close that\n * leaves the history as it was before the dialog, and only the opener knows whether it\n * may.\n */\nexport function useDialogParam(param: DialogParam): [boolean, (open: boolean) => void] {\n const { key, value } = typeof param === \"string\" ? { key: param, value: \"1\" } : param;\n const [params, setParams] = useSearchParams();\n const location = useLocation();\n const navigate = useNavigate();\n const open = typeof param === \"string\" ? params.has(key) : params.get(key) === value;\n const mark = `${key}=${value}`;\n\n const setOpen = (next: boolean) => {\n if (next === open) return;\n const state = carriedState(location.state);\n if (next) {\n setParams(\n (prev) => {\n const p = new URLSearchParams(prev);\n p.set(key, value);\n return p;\n },\n { state: { ...state, [OPENED_BY_KIT]: mark } },\n );\n return;\n }\n if (state?.[OPENED_BY_KIT] === mark) {\n void navigate(-1);\n return;\n }\n setParams(\n (prev) => {\n const p = new URLSearchParams(prev);\n p.delete(key);\n return p;\n },\n { replace: true, state },\n );\n };\n\n return [open, setOpen];\n}\n"],"mappings":"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","names":[]}
|
package/dist/i18n/defaults.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "../components/ui-labels.js";
|
|
15
15
|
import { DEFAULT_WIZARD_LABELS } from "../wizard/wizard-labels.js";
|
|
16
16
|
import { DEFAULT_TOUR_LABELS } from "../tour/tour-labels.js";
|
|
17
|
+
import { DEFAULT_TOUR_CATALOGUE_LABELS } from "../tour/tour-catalogue-labels.js";
|
|
17
18
|
import { DEFAULT_COMMAND_PALETTE_LABELS } from "../search/command-palette-labels.js";
|
|
18
19
|
import { DEFAULT_GLOBAL_SEARCH_LABELS } from "../search/global-search-labels.js";
|
|
19
20
|
import { DEFAULT_SERIES_CHART_LABELS } from "../components/series-chart-labels.js";
|
|
@@ -149,6 +150,7 @@ const DEFAULT_UI_KIT_LABELS = {
|
|
|
149
150
|
file: DEFAULT_FILE_LABELS,
|
|
150
151
|
wizard: DEFAULT_WIZARD_LABELS,
|
|
151
152
|
tour: DEFAULT_TOUR_LABELS,
|
|
153
|
+
tourCatalogue: DEFAULT_TOUR_CATALOGUE_LABELS,
|
|
152
154
|
commandPalette: DEFAULT_COMMAND_PALETTE_LABELS,
|
|
153
155
|
globalSearch: DEFAULT_GLOBAL_SEARCH_LABELS,
|
|
154
156
|
seriesChart: DEFAULT_SERIES_CHART_LABELS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/i18n/defaults.ts"],"sourcesContent":["import { DEFAULT_DATA_TABLE_LABELS } from \"../components/data-table-labels\";\nimport { DEFAULT_MINI_CALENDAR_LABELS } from \"../components/mini-calendar-labels\";\nimport { DEFAULT_CALENDAR_HEATMAP_LABELS } from \"../components/calendar-heatmap-labels\";\nimport { DEFAULT_MONTH_PICKER_LABELS } from \"../components/month-picker-labels\";\nimport { DEFAULT_PAGE_CONTENTS_LABELS } from \"../components/page-contents-labels\";\nimport { DEFAULT_POPOVER_LABELS } from \"../components/popover-labels\";\nimport { DEFAULT_CHIP_INPUT_LABELS } from \"../components/chip-labels\";\nimport { DEFAULT_FIELD_SYNC_LABELS } from \"../components/field-sync-labels\";\nimport {\n DEFAULT_CHARACTER_COUNT_LABELS,\n DEFAULT_PASSWORD_REVEAL_LABELS,\n DEFAULT_TABS_LABELS,\n} from \"../components/ui-labels\";\nimport { DEFAULT_WIZARD_LABELS } from \"../wizard/wizard-labels\";\nimport { DEFAULT_TOUR_LABELS } from \"../tour/tour-labels\";\nimport { DEFAULT_COMMAND_PALETTE_LABELS } from \"../search/command-palette-labels\";\nimport { DEFAULT_GLOBAL_SEARCH_LABELS } from \"../search/global-search-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS } from \"../components/series-chart-labels\";\nimport { DEFAULT_PIE_CHART_LABELS } from \"../components/pie-chart-labels\";\nimport { DEFAULT_SPARKLINE_LABELS } from \"../components/sparkline-labels\";\nimport { DEFAULT_STAT_TILE_LABELS } from \"../components/stat-tile-labels\";\nimport { DEFAULT_SIGNATURE_PAD_LABELS } from \"../components/signature-pad-labels\";\nimport { DEFAULT_PASSWORD_STRENGTH_LABELS } from \"../components/password-strength-labels\";\nimport { DEFAULT_DANGER_CONFIRM_LABELS } from \"../components/danger-confirm-labels\";\nimport { DEFAULT_SWATCH_PICKER_LABELS } from \"../components/swatch-picker-labels\";\nimport { DEFAULT_ICON_PICKER_LABELS } from \"../components/icon-picker-labels\";\nimport { DEFAULT_DIALOG_FRAME_LABELS } from \"../components/dialog-frame-labels\";\nimport { DEFAULT_FILE_PICKER_LABELS } from \"../hooks/use-file-picker-labels\";\nimport { DEFAULT_MEASURED_GRID_LABELS } from \"../components/measured-grid-labels\";\nimport { DEFAULT_CONFIRM_DIALOG_LABELS } from \"../components/confirm-dialog-labels\";\nimport { DEFAULT_FLOATING_PANEL_LABELS } from \"../components/floating-panel-labels\";\nimport { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from \"../feedback/feedback-attachment-labels\";\nimport { DEFAULT_FEEDBACK_DIALOG_LABELS } from \"../feedback/feedback-dialog-labels\";\nimport {\n DEFAULT_FEEDBACK_COMPOSER_LABELS,\n DEFAULT_FEEDBACK_THREAD_LABELS,\n} from \"../feedback/feedback-thread-labels\";\nimport {\n DEFAULT_FEEDBACK_CATEGORY_LABELS,\n DEFAULT_FEEDBACK_STATUS_LABELS,\n DEFAULT_FEEDBACK_TOAST_LABELS,\n} from \"../feedback/feedback-labels\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"../feedback/feedback-menu-labels\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS } from \"../feedback/feedback-public-labels\";\nimport { DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS } from \"../notifications/feedback-notification-types-labels\";\nimport { DEFAULT_FEEDBACK_CONTEXT_LABELS } from \"../feedback/feedback-context-labels\";\nimport { DEFAULT_FEEDBACK_PAGE_LABELS } from \"../feedback/feedback-table-labels\";\nimport { DEFAULT_FEEDBACK_DETAIL_LABELS } from \"../feedback/feedback-row-detail-labels\";\nimport {\n DEFAULT_FEEDBACK_COMMENTS_LABELS,\n DEFAULT_FEEDBACK_REPLIES_LABELS,\n DEFAULT_FEEDBACK_VOTE_LABELS,\n} from \"../feedback/feedback-thread-parts-labels\";\nimport { DEFAULT_ACCOUNT_SETTINGS_LABELS } from \"../account/account-settings-labels\";\nimport { DEFAULT_COPY_BUTTON_LABELS } from \"../components/copy-button-labels\";\nimport { DEFAULT_BULK_ACTION_BAR_LABELS } from \"../components/bulk-action-bar-labels\";\nimport { DEFAULT_LIST_LABELS } from \"../components/list-labels\";\nimport { DEFAULT_BREADCRUMBS_LABELS } from \"../components/breadcrumbs-labels\";\nimport { DEFAULT_TOAST_LABELS } from \"../components/toast-labels\";\nimport { DEFAULT_FORM_ACTIONS_LABELS } from \"../components/form-actions-labels\";\nimport { DEFAULT_DESCRIPTION_LIST_LABELS } from \"../components/description-list-labels\";\nimport { DEFAULT_LINE_ITEMS_LABELS } from \"../components/line-items-labels\";\nimport { DEFAULT_PROGRESS_BAR_LABELS } from \"../components/progress-bar-labels\";\nimport { DEFAULT_SIGNED_AMOUNT_LABELS } from \"../components/signed-amount-labels\";\nimport { DEFAULT_ERROR_BOUNDARY_LABELS } from \"../components/error-boundary-labels\";\nimport { DEFAULT_AUTHED_IMAGE_LABELS } from \"../components/authed-image-labels\";\nimport { DEFAULT_IMAGE_GRID_LABELS } from \"../components/image-grid-labels\";\nimport { DEFAULT_LIGHTBOX_LABELS } from \"../components/lightbox-labels\";\nimport { DEFAULT_WRITE_LOCK_LABELS } from \"../components/write-lock-labels\";\nimport { DEFAULT_LEGAL_LABELS } from \"../components/legal-labels\";\nimport { DEFAULT_SIGN_IN_LABELS } from \"../auth/sign-in-form-labels\";\nimport { DEFAULT_REGISTER_LABELS } from \"../auth/register-form-labels\";\nimport { DEFAULT_COMPLETE_NAME_LABELS } from \"../auth/complete-name-dialog-labels\";\nimport { DEFAULT_FORGOT_PASSWORD_LABELS } from \"../auth/forgot-password-form-labels\";\nimport { DEFAULT_RESET_PASSWORD_LABELS } from \"../auth/reset-password-form-labels\";\nimport { DEFAULT_VERIFY_EMAIL_LABELS } from \"../auth/verify-email-labels\";\nimport { DEFAULT_NOT_FOUND_LABELS } from \"../auth/not-found-page-labels\";\nimport { DEFAULT_ACCEPT_INVITATION_LABELS } from \"../auth/accept-invitation-labels\";\nimport { DEFAULT_COMPANY_SWITCHER_LABELS } from \"../shell/company-switcher-labels\";\nimport { DEFAULT_EMAIL_CHANGE_LABELS } from \"../account/email-change-setting-labels\";\nimport { DEFAULT_SESSIONS_LABELS } from \"../account/sessions-setting-labels\";\nimport { DEFAULT_DELETE_ACCOUNT_LABELS } from \"../account/delete-account-setting-labels\";\nimport { DEFAULT_DATA_EXPORT_LABELS } from \"../account/data-export-setting-labels\";\nimport { DEFAULT_USER_ROSTER_LABELS } from \"../admin/user-roster-labels\";\nimport { DEFAULT_ROLE_SELECT_LABELS } from \"../admin/role-select-labels\";\nimport { DEFAULT_REVIEWER_SCOPE_LABELS } from \"../admin/reviewer-scope-editor-labels\";\nimport { DEFAULT_ADMIN_ACTION_LABELS } from \"../admin/admin-action-confirm-labels\";\nimport { DEFAULT_ADMIN_ACTION_LOG_LABELS } from \"../admin/admin-action-log-labels\";\nimport { DEFAULT_TRANSFER_OWNERSHIP_LABELS } from \"../admin/transfer-ownership-dialog-labels\";\nimport { DEFAULT_INVITATIONS_LABELS } from \"../admin/invitations-panel-labels\";\nimport { DEFAULT_SETTINGS_LABELS } from \"../settings/settings-labels\";\nimport { DEFAULT_LANDING_LABELS } from \"../landing/landing-labels\";\nimport { DEFAULT_DEMO_LABELS } from \"../demo/demo-labels\";\nimport { DEFAULT_BILLING_LABELS } from \"../billing/billing-labels\";\nimport { DEFAULT_PLAN_CHANGE_LABELS } from \"../admin/plan-change-labels\";\nimport { DEFAULT_APPEARANCE_LABELS } from \"../components/appearance-labels\";\nimport { DEFAULT_DEVICE_RESET_LABELS } from \"../settings/device-reset-setting-labels\";\nimport { DEFAULT_ROW_ACTIONS_LABELS } from \"../components/row-actions-labels\";\nimport { DEFAULT_APP_SHELL_MORE_LABELS } from \"../shell/app-shell-labels\";\nimport { DEFAULT_ACCOUNT_STATE_LABELS } from \"../components/account-chips-labels\";\nimport { DEFAULT_ADMIN_AREA_LABELS } from \"../admin/admin-area-labels\";\nimport { DEFAULT_TOP_BAR_PARTS_LABELS } from \"../shell/top-bar-parts-labels\";\nimport { DEFAULT_SHARE_CARD_LABELS } from \"../components/share-card-labels\";\nimport { DEFAULT_REAUTH_DIALOG_LABELS } from \"../components/reauth-dialog-labels\";\nimport { DEFAULT_SERVER_WAKE_LABELS } from \"../components/server-wake-labels\";\nimport { DEFAULT_TRANSLATION_REVIEW_LABELS } from \"../components/translation-review-labels\";\nimport { DEFAULT_COUNTRY_SELECT_LABELS } from \"../components/country-select-labels\";\nimport { DEFAULT_INLINE_EDIT_LABELS } from \"../components/inline-edit-field-labels\";\nimport { DEFAULT_IBAN_INPUT_LABELS } from \"../components/iban-input-labels\";\nimport { DEFAULT_PHONE_INPUT_LABELS } from \"../components/phone-input-labels\";\nimport { DEFAULT_SIGN_CHIP_LABELS } from \"../components/sign-chip-labels\";\nimport { DEFAULT_COLUMN_MAPPER_LABELS } from \"../components/column-mapper-labels\";\nimport { DEFAULT_NOTIFICATIONS_LABELS } from \"../notifications/notifications-labels\";\nimport {\n DEFAULT_APP_SHELL_LABELS,\n DEFAULT_CALCULATOR_LABELS,\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n DEFAULT_CURRENCY_LABELS,\n DEFAULT_DATE_PICKER_LABELS,\n DEFAULT_FILE_LABELS,\n DEFAULT_MULTI_SELECT_LABELS,\n DEFAULT_PICKER_SHEET_LABELS,\n DEFAULT_SWIPEABLE_ROW_LABELS,\n DEFAULT_TOP_BAR_LABELS,\n} from \"./shared-labels\";\nimport type { UiKitLabels } from \"./kit-labels\";\n\n/**\n * The whole English tree in one object: what a translation is written against, and\n * the `reference` for {@link missingKitLabels}.\n *\n * A separate module from the provider on purpose. Components import the provider's\n * hooks; this file imports the components' defaults. Kept together they would be an\n * import cycle through every component in the kit.\n *\n * Every import here is a module of WORDS — a `*-labels.ts` beside its component, which\n * the component re-exports (`shared-labels` for the namespaces the provider types\n * itself, which `kit-labels` re-exports) — never a component. The\n * English catalogue (`locales/en.ts`) is built on this object, so a component imported\n * here rides along with English: in 0.33 that was 79 of them, and an app fetching `en`\n * lazily fetched ~40 chunks of component code with it, where every other language is one\n * (0.34, KS U8). `__tests__/english-catalogue-graph.test.ts` holds the line, and the\n * build makes `dist/i18n/locales/en.js` one file (scripts/flatten-en.mjs).\n */\nexport const DEFAULT_UI_KIT_LABELS: UiKitLabels = {\n common: DEFAULT_COMMON_LABELS,\n dataTable: DEFAULT_DATA_TABLE_LABELS,\n miniCalendar: DEFAULT_MINI_CALENDAR_LABELS,\n calendarHeatmap: DEFAULT_CALENDAR_HEATMAP_LABELS,\n datePicker: DEFAULT_DATE_PICKER_LABELS,\n monthPicker: DEFAULT_MONTH_PICKER_LABELS,\n popover: DEFAULT_POPOVER_LABELS,\n combobox: DEFAULT_COMBOBOX_LABELS,\n multiSelect: DEFAULT_MULTI_SELECT_LABELS,\n calculator: DEFAULT_CALCULATOR_LABELS,\n currency: DEFAULT_CURRENCY_LABELS,\n chipInput: DEFAULT_CHIP_INPUT_LABELS,\n fieldSync: DEFAULT_FIELD_SYNC_LABELS,\n passwordReveal: DEFAULT_PASSWORD_REVEAL_LABELS,\n tabs: DEFAULT_TABS_LABELS,\n appShell: DEFAULT_APP_SHELL_LABELS,\n pageContents: DEFAULT_PAGE_CONTENTS_LABELS,\n topBar: DEFAULT_TOP_BAR_LABELS,\n pickerSheet: DEFAULT_PICKER_SHEET_LABELS,\n swipeableRow: DEFAULT_SWIPEABLE_ROW_LABELS,\n file: DEFAULT_FILE_LABELS,\n wizard: DEFAULT_WIZARD_LABELS,\n tour: DEFAULT_TOUR_LABELS,\n commandPalette: DEFAULT_COMMAND_PALETTE_LABELS,\n globalSearch: DEFAULT_GLOBAL_SEARCH_LABELS,\n seriesChart: DEFAULT_SERIES_CHART_LABELS,\n pieChart: DEFAULT_PIE_CHART_LABELS,\n sparkline: DEFAULT_SPARKLINE_LABELS,\n statTile: DEFAULT_STAT_TILE_LABELS,\n signaturePad: DEFAULT_SIGNATURE_PAD_LABELS,\n passwordStrength: DEFAULT_PASSWORD_STRENGTH_LABELS,\n dangerConfirm: DEFAULT_DANGER_CONFIRM_LABELS,\n swatchPicker: DEFAULT_SWATCH_PICKER_LABELS,\n iconPicker: DEFAULT_ICON_PICKER_LABELS,\n dialogFrame: DEFAULT_DIALOG_FRAME_LABELS,\n filePicker: DEFAULT_FILE_PICKER_LABELS,\n measuredGrid: DEFAULT_MEASURED_GRID_LABELS,\n feedbackAttachment: DEFAULT_FEEDBACK_ATTACHMENT_LABELS,\n feedbackDialog: DEFAULT_FEEDBACK_DIALOG_LABELS,\n feedbackThread: DEFAULT_FEEDBACK_THREAD_LABELS,\n feedbackComposer: DEFAULT_FEEDBACK_COMPOSER_LABELS,\n feedbackStatus: DEFAULT_FEEDBACK_STATUS_LABELS,\n feedbackCategory: DEFAULT_FEEDBACK_CATEGORY_LABELS,\n feedbackToast: DEFAULT_FEEDBACK_TOAST_LABELS,\n feedbackMenu: DEFAULT_FEEDBACK_MENU_LABELS,\n feedbackPublic: DEFAULT_FEEDBACK_PUBLIC_LABELS,\n feedbackNotificationTypes: DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS,\n feedbackContext: DEFAULT_FEEDBACK_CONTEXT_LABELS,\n feedbackPage: DEFAULT_FEEDBACK_PAGE_LABELS,\n feedbackDetail: DEFAULT_FEEDBACK_DETAIL_LABELS,\n feedbackVote: DEFAULT_FEEDBACK_VOTE_LABELS,\n feedbackComments: DEFAULT_FEEDBACK_COMMENTS_LABELS,\n feedbackReplies: DEFAULT_FEEDBACK_REPLIES_LABELS,\n accountSettings: DEFAULT_ACCOUNT_SETTINGS_LABELS,\n confirmDialog: DEFAULT_CONFIRM_DIALOG_LABELS,\n floatingPanel: DEFAULT_FLOATING_PANEL_LABELS,\n copyButton: DEFAULT_COPY_BUTTON_LABELS,\n bulkActionBar: DEFAULT_BULK_ACTION_BAR_LABELS,\n list: DEFAULT_LIST_LABELS,\n breadcrumbs: DEFAULT_BREADCRUMBS_LABELS,\n toast: DEFAULT_TOAST_LABELS,\n form: DEFAULT_FORM_ACTIONS_LABELS,\n descriptionList: DEFAULT_DESCRIPTION_LIST_LABELS,\n lineItems: DEFAULT_LINE_ITEMS_LABELS,\n progressBar: DEFAULT_PROGRESS_BAR_LABELS,\n signedAmount: DEFAULT_SIGNED_AMOUNT_LABELS,\n errorBoundary: DEFAULT_ERROR_BOUNDARY_LABELS,\n authedImage: DEFAULT_AUTHED_IMAGE_LABELS,\n imageGrid: DEFAULT_IMAGE_GRID_LABELS,\n lightbox: DEFAULT_LIGHTBOX_LABELS,\n writeLock: DEFAULT_WRITE_LOCK_LABELS,\n accountState: DEFAULT_ACCOUNT_STATE_LABELS,\n shareCard: DEFAULT_SHARE_CARD_LABELS,\n reauthDialog: DEFAULT_REAUTH_DIALOG_LABELS,\n serverWake: DEFAULT_SERVER_WAKE_LABELS,\n translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,\n legal: DEFAULT_LEGAL_LABELS,\n signIn: DEFAULT_SIGN_IN_LABELS,\n register: DEFAULT_REGISTER_LABELS,\n completeName: DEFAULT_COMPLETE_NAME_LABELS,\n forgotPassword: DEFAULT_FORGOT_PASSWORD_LABELS,\n resetPassword: DEFAULT_RESET_PASSWORD_LABELS,\n verifyEmail: DEFAULT_VERIFY_EMAIL_LABELS,\n notFound: DEFAULT_NOT_FOUND_LABELS,\n acceptInvitation: DEFAULT_ACCEPT_INVITATION_LABELS,\n companySwitcher: DEFAULT_COMPANY_SWITCHER_LABELS,\n emailChange: DEFAULT_EMAIL_CHANGE_LABELS,\n sessions: DEFAULT_SESSIONS_LABELS,\n deleteAccount: DEFAULT_DELETE_ACCOUNT_LABELS,\n dataExport: DEFAULT_DATA_EXPORT_LABELS,\n userRoster: DEFAULT_USER_ROSTER_LABELS,\n roleSelect: DEFAULT_ROLE_SELECT_LABELS,\n reviewerScope: DEFAULT_REVIEWER_SCOPE_LABELS,\n adminAction: DEFAULT_ADMIN_ACTION_LABELS,\n adminActionLog: DEFAULT_ADMIN_ACTION_LOG_LABELS,\n transferOwnership: DEFAULT_TRANSFER_OWNERSHIP_LABELS,\n invitations: DEFAULT_INVITATIONS_LABELS,\n settings: DEFAULT_SETTINGS_LABELS,\n landing: DEFAULT_LANDING_LABELS,\n demo: DEFAULT_DEMO_LABELS,\n billing: DEFAULT_BILLING_LABELS,\n planChange: DEFAULT_PLAN_CHANGE_LABELS,\n appearance: DEFAULT_APPEARANCE_LABELS,\n deviceReset: DEFAULT_DEVICE_RESET_LABELS,\n rowActions: DEFAULT_ROW_ACTIONS_LABELS,\n appShellMore: DEFAULT_APP_SHELL_MORE_LABELS,\n characterCount: DEFAULT_CHARACTER_COUNT_LABELS,\n countrySelect: DEFAULT_COUNTRY_SELECT_LABELS,\n inlineEdit: DEFAULT_INLINE_EDIT_LABELS,\n ibanInput: DEFAULT_IBAN_INPUT_LABELS,\n phoneInput: DEFAULT_PHONE_INPUT_LABELS,\n signChip: DEFAULT_SIGN_CHIP_LABELS,\n columnMapper: DEFAULT_COLUMN_MAPPER_LABELS,\n notifications: DEFAULT_NOTIFICATIONS_LABELS,\n adminArea: DEFAULT_ADMIN_AREA_LABELS,\n topBarParts: DEFAULT_TOP_BAR_PARTS_LABELS,\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/i18n/defaults.ts"],"sourcesContent":["import { DEFAULT_DATA_TABLE_LABELS } from \"../components/data-table-labels\";\nimport { DEFAULT_MINI_CALENDAR_LABELS } from \"../components/mini-calendar-labels\";\nimport { DEFAULT_CALENDAR_HEATMAP_LABELS } from \"../components/calendar-heatmap-labels\";\nimport { DEFAULT_MONTH_PICKER_LABELS } from \"../components/month-picker-labels\";\nimport { DEFAULT_PAGE_CONTENTS_LABELS } from \"../components/page-contents-labels\";\nimport { DEFAULT_POPOVER_LABELS } from \"../components/popover-labels\";\nimport { DEFAULT_CHIP_INPUT_LABELS } from \"../components/chip-labels\";\nimport { DEFAULT_FIELD_SYNC_LABELS } from \"../components/field-sync-labels\";\nimport {\n DEFAULT_CHARACTER_COUNT_LABELS,\n DEFAULT_PASSWORD_REVEAL_LABELS,\n DEFAULT_TABS_LABELS,\n} from \"../components/ui-labels\";\nimport { DEFAULT_WIZARD_LABELS } from \"../wizard/wizard-labels\";\nimport { DEFAULT_TOUR_LABELS } from \"../tour/tour-labels\";\nimport { DEFAULT_TOUR_CATALOGUE_LABELS } from \"../tour/tour-catalogue-labels\";\nimport { DEFAULT_COMMAND_PALETTE_LABELS } from \"../search/command-palette-labels\";\nimport { DEFAULT_GLOBAL_SEARCH_LABELS } from \"../search/global-search-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS } from \"../components/series-chart-labels\";\nimport { DEFAULT_PIE_CHART_LABELS } from \"../components/pie-chart-labels\";\nimport { DEFAULT_SPARKLINE_LABELS } from \"../components/sparkline-labels\";\nimport { DEFAULT_STAT_TILE_LABELS } from \"../components/stat-tile-labels\";\nimport { DEFAULT_SIGNATURE_PAD_LABELS } from \"../components/signature-pad-labels\";\nimport { DEFAULT_PASSWORD_STRENGTH_LABELS } from \"../components/password-strength-labels\";\nimport { DEFAULT_DANGER_CONFIRM_LABELS } from \"../components/danger-confirm-labels\";\nimport { DEFAULT_SWATCH_PICKER_LABELS } from \"../components/swatch-picker-labels\";\nimport { DEFAULT_ICON_PICKER_LABELS } from \"../components/icon-picker-labels\";\nimport { DEFAULT_DIALOG_FRAME_LABELS } from \"../components/dialog-frame-labels\";\nimport { DEFAULT_FILE_PICKER_LABELS } from \"../hooks/use-file-picker-labels\";\nimport { DEFAULT_MEASURED_GRID_LABELS } from \"../components/measured-grid-labels\";\nimport { DEFAULT_CONFIRM_DIALOG_LABELS } from \"../components/confirm-dialog-labels\";\nimport { DEFAULT_FLOATING_PANEL_LABELS } from \"../components/floating-panel-labels\";\nimport { DEFAULT_FEEDBACK_ATTACHMENT_LABELS } from \"../feedback/feedback-attachment-labels\";\nimport { DEFAULT_FEEDBACK_DIALOG_LABELS } from \"../feedback/feedback-dialog-labels\";\nimport {\n DEFAULT_FEEDBACK_COMPOSER_LABELS,\n DEFAULT_FEEDBACK_THREAD_LABELS,\n} from \"../feedback/feedback-thread-labels\";\nimport {\n DEFAULT_FEEDBACK_CATEGORY_LABELS,\n DEFAULT_FEEDBACK_STATUS_LABELS,\n DEFAULT_FEEDBACK_TOAST_LABELS,\n} from \"../feedback/feedback-labels\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"../feedback/feedback-menu-labels\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS } from \"../feedback/feedback-public-labels\";\nimport { DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS } from \"../notifications/feedback-notification-types-labels\";\nimport { DEFAULT_FEEDBACK_CONTEXT_LABELS } from \"../feedback/feedback-context-labels\";\nimport { DEFAULT_FEEDBACK_PAGE_LABELS } from \"../feedback/feedback-table-labels\";\nimport { DEFAULT_FEEDBACK_DETAIL_LABELS } from \"../feedback/feedback-row-detail-labels\";\nimport {\n DEFAULT_FEEDBACK_COMMENTS_LABELS,\n DEFAULT_FEEDBACK_REPLIES_LABELS,\n DEFAULT_FEEDBACK_VOTE_LABELS,\n} from \"../feedback/feedback-thread-parts-labels\";\nimport { DEFAULT_ACCOUNT_SETTINGS_LABELS } from \"../account/account-settings-labels\";\nimport { DEFAULT_COPY_BUTTON_LABELS } from \"../components/copy-button-labels\";\nimport { DEFAULT_BULK_ACTION_BAR_LABELS } from \"../components/bulk-action-bar-labels\";\nimport { DEFAULT_LIST_LABELS } from \"../components/list-labels\";\nimport { DEFAULT_BREADCRUMBS_LABELS } from \"../components/breadcrumbs-labels\";\nimport { DEFAULT_TOAST_LABELS } from \"../components/toast-labels\";\nimport { DEFAULT_FORM_ACTIONS_LABELS } from \"../components/form-actions-labels\";\nimport { DEFAULT_DESCRIPTION_LIST_LABELS } from \"../components/description-list-labels\";\nimport { DEFAULT_LINE_ITEMS_LABELS } from \"../components/line-items-labels\";\nimport { DEFAULT_PROGRESS_BAR_LABELS } from \"../components/progress-bar-labels\";\nimport { DEFAULT_SIGNED_AMOUNT_LABELS } from \"../components/signed-amount-labels\";\nimport { DEFAULT_ERROR_BOUNDARY_LABELS } from \"../components/error-boundary-labels\";\nimport { DEFAULT_AUTHED_IMAGE_LABELS } from \"../components/authed-image-labels\";\nimport { DEFAULT_IMAGE_GRID_LABELS } from \"../components/image-grid-labels\";\nimport { DEFAULT_LIGHTBOX_LABELS } from \"../components/lightbox-labels\";\nimport { DEFAULT_WRITE_LOCK_LABELS } from \"../components/write-lock-labels\";\nimport { DEFAULT_LEGAL_LABELS } from \"../components/legal-labels\";\nimport { DEFAULT_SIGN_IN_LABELS } from \"../auth/sign-in-form-labels\";\nimport { DEFAULT_REGISTER_LABELS } from \"../auth/register-form-labels\";\nimport { DEFAULT_COMPLETE_NAME_LABELS } from \"../auth/complete-name-dialog-labels\";\nimport { DEFAULT_FORGOT_PASSWORD_LABELS } from \"../auth/forgot-password-form-labels\";\nimport { DEFAULT_RESET_PASSWORD_LABELS } from \"../auth/reset-password-form-labels\";\nimport { DEFAULT_VERIFY_EMAIL_LABELS } from \"../auth/verify-email-labels\";\nimport { DEFAULT_NOT_FOUND_LABELS } from \"../auth/not-found-page-labels\";\nimport { DEFAULT_ACCEPT_INVITATION_LABELS } from \"../auth/accept-invitation-labels\";\nimport { DEFAULT_COMPANY_SWITCHER_LABELS } from \"../shell/company-switcher-labels\";\nimport { DEFAULT_EMAIL_CHANGE_LABELS } from \"../account/email-change-setting-labels\";\nimport { DEFAULT_SESSIONS_LABELS } from \"../account/sessions-setting-labels\";\nimport { DEFAULT_DELETE_ACCOUNT_LABELS } from \"../account/delete-account-setting-labels\";\nimport { DEFAULT_DATA_EXPORT_LABELS } from \"../account/data-export-setting-labels\";\nimport { DEFAULT_USER_ROSTER_LABELS } from \"../admin/user-roster-labels\";\nimport { DEFAULT_ROLE_SELECT_LABELS } from \"../admin/role-select-labels\";\nimport { DEFAULT_REVIEWER_SCOPE_LABELS } from \"../admin/reviewer-scope-editor-labels\";\nimport { DEFAULT_ADMIN_ACTION_LABELS } from \"../admin/admin-action-confirm-labels\";\nimport { DEFAULT_ADMIN_ACTION_LOG_LABELS } from \"../admin/admin-action-log-labels\";\nimport { DEFAULT_TRANSFER_OWNERSHIP_LABELS } from \"../admin/transfer-ownership-dialog-labels\";\nimport { DEFAULT_INVITATIONS_LABELS } from \"../admin/invitations-panel-labels\";\nimport { DEFAULT_SETTINGS_LABELS } from \"../settings/settings-labels\";\nimport { DEFAULT_LANDING_LABELS } from \"../landing/landing-labels\";\nimport { DEFAULT_DEMO_LABELS } from \"../demo/demo-labels\";\nimport { DEFAULT_BILLING_LABELS } from \"../billing/billing-labels\";\nimport { DEFAULT_PLAN_CHANGE_LABELS } from \"../admin/plan-change-labels\";\nimport { DEFAULT_APPEARANCE_LABELS } from \"../components/appearance-labels\";\nimport { DEFAULT_DEVICE_RESET_LABELS } from \"../settings/device-reset-setting-labels\";\nimport { DEFAULT_ROW_ACTIONS_LABELS } from \"../components/row-actions-labels\";\nimport { DEFAULT_APP_SHELL_MORE_LABELS } from \"../shell/app-shell-labels\";\nimport { DEFAULT_ACCOUNT_STATE_LABELS } from \"../components/account-chips-labels\";\nimport { DEFAULT_ADMIN_AREA_LABELS } from \"../admin/admin-area-labels\";\nimport { DEFAULT_TOP_BAR_PARTS_LABELS } from \"../shell/top-bar-parts-labels\";\nimport { DEFAULT_SHARE_CARD_LABELS } from \"../components/share-card-labels\";\nimport { DEFAULT_REAUTH_DIALOG_LABELS } from \"../components/reauth-dialog-labels\";\nimport { DEFAULT_SERVER_WAKE_LABELS } from \"../components/server-wake-labels\";\nimport { DEFAULT_TRANSLATION_REVIEW_LABELS } from \"../components/translation-review-labels\";\nimport { DEFAULT_COUNTRY_SELECT_LABELS } from \"../components/country-select-labels\";\nimport { DEFAULT_INLINE_EDIT_LABELS } from \"../components/inline-edit-field-labels\";\nimport { DEFAULT_IBAN_INPUT_LABELS } from \"../components/iban-input-labels\";\nimport { DEFAULT_PHONE_INPUT_LABELS } from \"../components/phone-input-labels\";\nimport { DEFAULT_SIGN_CHIP_LABELS } from \"../components/sign-chip-labels\";\nimport { DEFAULT_COLUMN_MAPPER_LABELS } from \"../components/column-mapper-labels\";\nimport { DEFAULT_NOTIFICATIONS_LABELS } from \"../notifications/notifications-labels\";\nimport {\n DEFAULT_APP_SHELL_LABELS,\n DEFAULT_CALCULATOR_LABELS,\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n DEFAULT_CURRENCY_LABELS,\n DEFAULT_DATE_PICKER_LABELS,\n DEFAULT_FILE_LABELS,\n DEFAULT_MULTI_SELECT_LABELS,\n DEFAULT_PICKER_SHEET_LABELS,\n DEFAULT_SWIPEABLE_ROW_LABELS,\n DEFAULT_TOP_BAR_LABELS,\n} from \"./shared-labels\";\nimport type { UiKitLabels } from \"./kit-labels\";\n\n/**\n * The whole English tree in one object: what a translation is written against, and\n * the `reference` for {@link missingKitLabels}.\n *\n * A separate module from the provider on purpose. Components import the provider's\n * hooks; this file imports the components' defaults. Kept together they would be an\n * import cycle through every component in the kit.\n *\n * Every import here is a module of WORDS — a `*-labels.ts` beside its component, which\n * the component re-exports (`shared-labels` for the namespaces the provider types\n * itself, which `kit-labels` re-exports) — never a component. The\n * English catalogue (`locales/en.ts`) is built on this object, so a component imported\n * here rides along with English: in 0.33 that was 79 of them, and an app fetching `en`\n * lazily fetched ~40 chunks of component code with it, where every other language is one\n * (0.34, KS U8). `__tests__/english-catalogue-graph.test.ts` holds the line, and the\n * build makes `dist/i18n/locales/en.js` one file (scripts/flatten-en.mjs).\n */\nexport const DEFAULT_UI_KIT_LABELS: UiKitLabels = {\n common: DEFAULT_COMMON_LABELS,\n dataTable: DEFAULT_DATA_TABLE_LABELS,\n miniCalendar: DEFAULT_MINI_CALENDAR_LABELS,\n calendarHeatmap: DEFAULT_CALENDAR_HEATMAP_LABELS,\n datePicker: DEFAULT_DATE_PICKER_LABELS,\n monthPicker: DEFAULT_MONTH_PICKER_LABELS,\n popover: DEFAULT_POPOVER_LABELS,\n combobox: DEFAULT_COMBOBOX_LABELS,\n multiSelect: DEFAULT_MULTI_SELECT_LABELS,\n calculator: DEFAULT_CALCULATOR_LABELS,\n currency: DEFAULT_CURRENCY_LABELS,\n chipInput: DEFAULT_CHIP_INPUT_LABELS,\n fieldSync: DEFAULT_FIELD_SYNC_LABELS,\n passwordReveal: DEFAULT_PASSWORD_REVEAL_LABELS,\n tabs: DEFAULT_TABS_LABELS,\n appShell: DEFAULT_APP_SHELL_LABELS,\n pageContents: DEFAULT_PAGE_CONTENTS_LABELS,\n topBar: DEFAULT_TOP_BAR_LABELS,\n pickerSheet: DEFAULT_PICKER_SHEET_LABELS,\n swipeableRow: DEFAULT_SWIPEABLE_ROW_LABELS,\n file: DEFAULT_FILE_LABELS,\n wizard: DEFAULT_WIZARD_LABELS,\n tour: DEFAULT_TOUR_LABELS,\n tourCatalogue: DEFAULT_TOUR_CATALOGUE_LABELS,\n commandPalette: DEFAULT_COMMAND_PALETTE_LABELS,\n globalSearch: DEFAULT_GLOBAL_SEARCH_LABELS,\n seriesChart: DEFAULT_SERIES_CHART_LABELS,\n pieChart: DEFAULT_PIE_CHART_LABELS,\n sparkline: DEFAULT_SPARKLINE_LABELS,\n statTile: DEFAULT_STAT_TILE_LABELS,\n signaturePad: DEFAULT_SIGNATURE_PAD_LABELS,\n passwordStrength: DEFAULT_PASSWORD_STRENGTH_LABELS,\n dangerConfirm: DEFAULT_DANGER_CONFIRM_LABELS,\n swatchPicker: DEFAULT_SWATCH_PICKER_LABELS,\n iconPicker: DEFAULT_ICON_PICKER_LABELS,\n dialogFrame: DEFAULT_DIALOG_FRAME_LABELS,\n filePicker: DEFAULT_FILE_PICKER_LABELS,\n measuredGrid: DEFAULT_MEASURED_GRID_LABELS,\n feedbackAttachment: DEFAULT_FEEDBACK_ATTACHMENT_LABELS,\n feedbackDialog: DEFAULT_FEEDBACK_DIALOG_LABELS,\n feedbackThread: DEFAULT_FEEDBACK_THREAD_LABELS,\n feedbackComposer: DEFAULT_FEEDBACK_COMPOSER_LABELS,\n feedbackStatus: DEFAULT_FEEDBACK_STATUS_LABELS,\n feedbackCategory: DEFAULT_FEEDBACK_CATEGORY_LABELS,\n feedbackToast: DEFAULT_FEEDBACK_TOAST_LABELS,\n feedbackMenu: DEFAULT_FEEDBACK_MENU_LABELS,\n feedbackPublic: DEFAULT_FEEDBACK_PUBLIC_LABELS,\n feedbackNotificationTypes: DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS,\n feedbackContext: DEFAULT_FEEDBACK_CONTEXT_LABELS,\n feedbackPage: DEFAULT_FEEDBACK_PAGE_LABELS,\n feedbackDetail: DEFAULT_FEEDBACK_DETAIL_LABELS,\n feedbackVote: DEFAULT_FEEDBACK_VOTE_LABELS,\n feedbackComments: DEFAULT_FEEDBACK_COMMENTS_LABELS,\n feedbackReplies: DEFAULT_FEEDBACK_REPLIES_LABELS,\n accountSettings: DEFAULT_ACCOUNT_SETTINGS_LABELS,\n confirmDialog: DEFAULT_CONFIRM_DIALOG_LABELS,\n floatingPanel: DEFAULT_FLOATING_PANEL_LABELS,\n copyButton: DEFAULT_COPY_BUTTON_LABELS,\n bulkActionBar: DEFAULT_BULK_ACTION_BAR_LABELS,\n list: DEFAULT_LIST_LABELS,\n breadcrumbs: DEFAULT_BREADCRUMBS_LABELS,\n toast: DEFAULT_TOAST_LABELS,\n form: DEFAULT_FORM_ACTIONS_LABELS,\n descriptionList: DEFAULT_DESCRIPTION_LIST_LABELS,\n lineItems: DEFAULT_LINE_ITEMS_LABELS,\n progressBar: DEFAULT_PROGRESS_BAR_LABELS,\n signedAmount: DEFAULT_SIGNED_AMOUNT_LABELS,\n errorBoundary: DEFAULT_ERROR_BOUNDARY_LABELS,\n authedImage: DEFAULT_AUTHED_IMAGE_LABELS,\n imageGrid: DEFAULT_IMAGE_GRID_LABELS,\n lightbox: DEFAULT_LIGHTBOX_LABELS,\n writeLock: DEFAULT_WRITE_LOCK_LABELS,\n accountState: DEFAULT_ACCOUNT_STATE_LABELS,\n shareCard: DEFAULT_SHARE_CARD_LABELS,\n reauthDialog: DEFAULT_REAUTH_DIALOG_LABELS,\n serverWake: DEFAULT_SERVER_WAKE_LABELS,\n translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,\n legal: DEFAULT_LEGAL_LABELS,\n signIn: DEFAULT_SIGN_IN_LABELS,\n register: DEFAULT_REGISTER_LABELS,\n completeName: DEFAULT_COMPLETE_NAME_LABELS,\n forgotPassword: DEFAULT_FORGOT_PASSWORD_LABELS,\n resetPassword: DEFAULT_RESET_PASSWORD_LABELS,\n verifyEmail: DEFAULT_VERIFY_EMAIL_LABELS,\n notFound: DEFAULT_NOT_FOUND_LABELS,\n acceptInvitation: DEFAULT_ACCEPT_INVITATION_LABELS,\n companySwitcher: DEFAULT_COMPANY_SWITCHER_LABELS,\n emailChange: DEFAULT_EMAIL_CHANGE_LABELS,\n sessions: DEFAULT_SESSIONS_LABELS,\n deleteAccount: DEFAULT_DELETE_ACCOUNT_LABELS,\n dataExport: DEFAULT_DATA_EXPORT_LABELS,\n userRoster: DEFAULT_USER_ROSTER_LABELS,\n roleSelect: DEFAULT_ROLE_SELECT_LABELS,\n reviewerScope: DEFAULT_REVIEWER_SCOPE_LABELS,\n adminAction: DEFAULT_ADMIN_ACTION_LABELS,\n adminActionLog: DEFAULT_ADMIN_ACTION_LOG_LABELS,\n transferOwnership: DEFAULT_TRANSFER_OWNERSHIP_LABELS,\n invitations: DEFAULT_INVITATIONS_LABELS,\n settings: DEFAULT_SETTINGS_LABELS,\n landing: DEFAULT_LANDING_LABELS,\n demo: DEFAULT_DEMO_LABELS,\n billing: DEFAULT_BILLING_LABELS,\n planChange: DEFAULT_PLAN_CHANGE_LABELS,\n appearance: DEFAULT_APPEARANCE_LABELS,\n deviceReset: DEFAULT_DEVICE_RESET_LABELS,\n rowActions: DEFAULT_ROW_ACTIONS_LABELS,\n appShellMore: DEFAULT_APP_SHELL_MORE_LABELS,\n characterCount: DEFAULT_CHARACTER_COUNT_LABELS,\n countrySelect: DEFAULT_COUNTRY_SELECT_LABELS,\n inlineEdit: DEFAULT_INLINE_EDIT_LABELS,\n ibanInput: DEFAULT_IBAN_INPUT_LABELS,\n phoneInput: DEFAULT_PHONE_INPUT_LABELS,\n signChip: DEFAULT_SIGN_CHIP_LABELS,\n columnMapper: DEFAULT_COLUMN_MAPPER_LABELS,\n notifications: DEFAULT_NOTIFICATIONS_LABELS,\n adminArea: DEFAULT_ADMIN_AREA_LABELS,\n topBarParts: DEFAULT_TOP_BAR_PARTS_LABELS,\n};\n"],"mappings":"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","names":[]}
|
package/dist/i18n/german.js
CHANGED
|
@@ -581,6 +581,34 @@ function germanLabels(numberLocale) {
|
|
|
581
581
|
awaitClickHint: "Klicken Sie auf das hervorgehobene Element, um fortzufahren",
|
|
582
582
|
step: (current, total) => `${n(current)} / ${n(total)}`
|
|
583
583
|
},
|
|
584
|
+
// 0.43.0 — docs/tours-harmonization.md §3, §8: the tours page. keksdose's reviewed words.
|
|
585
|
+
tourCatalogue: {
|
|
586
|
+
searchLabel: "Touren durchsuchen",
|
|
587
|
+
searchPlaceholder: "Touren durchsuchen\u2026",
|
|
588
|
+
groupLabel: "Thema",
|
|
589
|
+
groupAll: (count) => `Alle Themen (${n(count)})`,
|
|
590
|
+
groupOption: (group, count) => `${group} (${n(count)})`,
|
|
591
|
+
stateLabel: "Touren filtern",
|
|
592
|
+
stateAll: (count) => `Alle (${n(count)})`,
|
|
593
|
+
stateTodo: (count) => `Offen (${n(count)})`,
|
|
594
|
+
stateDone: (count) => `Abgeschlossen (${n(count)})`,
|
|
595
|
+
showing: (shown, total) => `${n(shown)} von ${n(total)} Touren`,
|
|
596
|
+
noMatches: "Keine Tour passt",
|
|
597
|
+
noMatchesHint: "Versuchen Sie ein anderes Wort, oder setzen Sie die Filter zur\xFCck.",
|
|
598
|
+
clearFilters: "Filter zur\xFCcksetzen",
|
|
599
|
+
badgeNew: "Neu",
|
|
600
|
+
badgeUpdated: "Aktualisiert",
|
|
601
|
+
resetOn: (date, reason) => reason ? `Zur\xFCckgesetzt am ${date}: ${reason}` : `Zur\xFCckgesetzt am ${date}`,
|
|
602
|
+
points: (count) => `+${n(count)} Pkt.`,
|
|
603
|
+
steps: (count) => count === 1 ? `${n(count)} Schritt` : `${n(count)} Schritte`,
|
|
604
|
+
duration: (minutes) => `Etwa ${n(minutes)} Min.`,
|
|
605
|
+
completedOn: (date) => `Abgeschlossen am ${date}`,
|
|
606
|
+
completed: "Abgeschlossen",
|
|
607
|
+
notCompleted: "Nicht abgeschlossen",
|
|
608
|
+
markUndone: "Als nicht erledigt markieren",
|
|
609
|
+
start: "Starten",
|
|
610
|
+
replay: "Erneut"
|
|
611
|
+
},
|
|
584
612
|
commandPalette: {
|
|
585
613
|
clear: "Suche l\xF6schen",
|
|
586
614
|
submit: "Suchen",
|
|
@@ -1652,6 +1680,7 @@ Die vollst\xE4ndige Postanschrift wird betroffenen Personen und Aufsichtsbeh\xF6
|
|
|
1652
1680
|
// §312k BGB: the confirmation button says exactly this.
|
|
1653
1681
|
cancelConfirmAction: "Jetzt k\xFCndigen",
|
|
1654
1682
|
keepSubscription: "Abonnement behalten",
|
|
1683
|
+
portalOpensNewTab: (provider, app) => `Zahlung und Rechnungen \xF6ffnen sich bei ${provider} in einem neuen Tab; ${app ? `${app} bleibt hier offen` : "diese Seite bleibt offen"}.`,
|
|
1655
1684
|
grantWarningTitle: "Ihr kostenloser Zugang l\xE4uft weiter",
|
|
1656
1685
|
grantWarningBody: "Ihr kostenloser Zugang hat kein Enddatum. Solange er l\xE4uft, \xE4ndert ein Kauf Ihren Tarif nicht: Sie w\xFCrden f\xFCr einen Tarif bezahlen, der nicht gilt.",
|
|
1657
1686
|
grantWarningAction: "Trotzdem kaufen",
|
|
@@ -1735,7 +1764,7 @@ Die vollst\xE4ndige Postanschrift wird betroffenen Personen und Aufsichtsbeh\xF6
|
|
|
1735
1764
|
sections: {
|
|
1736
1765
|
overview: { title: "\xDCbersicht", help: "Was gerade wartet, auf einen Blick." },
|
|
1737
1766
|
users: { title: "Benutzer", help: "Wer ein Konto hat und was jede Person darf." },
|
|
1738
|
-
invitations: { title: "Einladungen", help: "
|
|
1767
|
+
invitations: { title: "Einladungen", help: "Laden Sie Personen per E-Mail in ihrer Sprache ein; senden Sie offene Einladungen erneut oder widerrufen Sie sie." },
|
|
1739
1768
|
billing: { title: "Abrechnung", help: "Tarif, Zahlung und Rechnungen." },
|
|
1740
1769
|
activity: { title: "Aktivit\xE4t", help: "Was in der Verwaltung getan wurde, das Neueste zuerst." }
|
|
1741
1770
|
},
|
|
@@ -1746,6 +1775,8 @@ Die vollst\xE4ndige Postanschrift wird betroffenen Personen und Aufsichtsbeh\xF6
|
|
|
1746
1775
|
users: "Benutzer",
|
|
1747
1776
|
usersNew: (count) => `${n(count)} neu in 7 Tagen`
|
|
1748
1777
|
},
|
|
1778
|
+
overviewError: "Die \xDCbersicht konnte nicht geladen werden.",
|
|
1779
|
+
retry: "Erneut versuchen",
|
|
1749
1780
|
tools: "Werkzeuge"
|
|
1750
1781
|
},
|
|
1751
1782
|
// 0.42.0 — docs/top-bar-harmonization.md §3.1: the top bar's shared parts. keksdose's
|
|
@@ -1758,7 +1789,10 @@ Die vollst\xE4ndige Postanschrift wird betroffenen Personen und Aufsichtsbeh\xF6
|
|
|
1758
1789
|
newTours: "Neue Touren",
|
|
1759
1790
|
allTours: "Alle Touren\u2026",
|
|
1760
1791
|
badgeNew: "Neu",
|
|
1761
|
-
completed: "Abgeschlossen"
|
|
1792
|
+
completed: "Abgeschlossen",
|
|
1793
|
+
badgeUpdated: "Aktualisiert",
|
|
1794
|
+
updatedDot: "Aktualisierte Touren",
|
|
1795
|
+
newAndUpdated: "Neu und aktualisiert"
|
|
1762
1796
|
},
|
|
1763
1797
|
sync: {
|
|
1764
1798
|
blocked: (count) => count === 1 ? `${n(count)} \xC4nderung kann von diesem Ger\xE4t nicht gesendet werden` : `${n(count)} \xC4nderungen k\xF6nnen von diesem Ger\xE4t nicht gesendet werden`,
|