@eifi1/ui-kit 0.30.0 → 0.31.1
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 +42 -5
- package/dist/account/data-export-setting.js +3 -2
- package/dist/account/data-export-setting.js.map +1 -1
- package/dist/account/delete-account-setting.d.ts +5 -1
- package/dist/account/delete-account-setting.js +3 -2
- package/dist/account/delete-account-setting.js.map +1 -1
- package/dist/account/email-change-setting.js +4 -3
- package/dist/account/email-change-setting.js.map +1 -1
- package/dist/account/sessions-setting.js +3 -2
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.d.ts +5 -1
- package/dist/admin/admin-parts.d.ts +8 -1
- package/dist/admin/admin-parts.js +1 -1
- package/dist/admin/admin-parts.js.map +1 -1
- package/dist/admin/invitations-panel.d.ts +5 -1
- package/dist/admin/invitations-panel.js +3 -2
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/reviewer-scope-editor.d.ts +5 -1
- package/dist/admin/role-select.d.ts +5 -1
- package/dist/admin/transfer-ownership-dialog.d.ts +5 -1
- package/dist/admin/user-roster.d.ts +5 -1
- package/dist/auth/auth-errors.d.ts +24 -6
- package/dist/auth/auth-errors.js +7 -1
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/register-form.d.ts +5 -1
- package/dist/auth/reset-password-form.d.ts +1 -1
- package/dist/auth/reset-password-form.js +2 -1
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/sign-in-form.d.ts +15 -1
- package/dist/auth/sign-in-form.js +12 -3
- package/dist/auth/sign-in-form.js.map +1 -1
- package/dist/auth/verify-email.d.ts +4 -0
- package/dist/auth/verify-email.js +3 -2
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/chart.d.ts +5 -1
- package/dist/components/account-settings-labels.d.ts +12 -0
- package/dist/components/account-settings-labels.js +2 -1
- package/dist/components/account-settings-labels.js.map +1 -1
- package/dist/components/account-settings.d.ts +8 -2
- package/dist/components/account-settings.js +19 -11
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +5 -1
- package/dist/components/button-group.d.ts +5 -1
- package/dist/components/calculator.d.ts +5 -1
- package/dist/components/confirm-dialog.d.ts +5 -1
- package/dist/components/confirm-dialog.js +7 -1
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -1
- package/dist/components/danger-confirm.d.ts +5 -1
- package/dist/components/data-table.js +8 -1
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.js +9 -4
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/facing-pair.d.ts +5 -1
- package/dist/components/file-button.d.ts +5 -1
- package/dist/components/file-dropzone.d.ts +5 -1
- package/dist/components/form-actions.d.ts +5 -1
- package/dist/components/iban-input.d.ts +5 -1
- package/dist/components/language-select.d.ts +5 -1
- package/dist/components/legal-page.d.ts +5 -1
- package/dist/components/legal.d.ts +5 -1
- package/dist/components/money-field.d.ts +5 -1
- package/dist/components/number-field.d.ts +5 -1
- package/dist/components/number-input.d.ts +5 -1
- package/dist/components/numpad-sheet.d.ts +5 -1
- package/dist/components/passkeys-setting.js +2 -1
- package/dist/components/passkeys-setting.js.map +1 -1
- package/dist/components/phone-input.d.ts +5 -1
- package/dist/components/series-chart.d.ts +5 -1
- package/dist/components/settings-fields.d.ts +40 -3
- package/dist/components/settings-fields.js +20 -1
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/share-card.d.ts +5 -1
- package/dist/components/text-link.d.ts +5 -1
- package/dist/components/time-input.d.ts +5 -1
- package/dist/components/ui.d.ts +5 -1
- package/dist/{delete-account-setting-CGVMckMw.d.ts → delete-account-setting-CpIhzxxZ.d.ts} +60 -23
- package/dist/demo/demo-banner.d.ts +51 -0
- package/dist/demo/demo-banner.js +97 -0
- package/dist/demo/demo-banner.js.map +1 -0
- package/dist/demo/demo-countdown.d.ts +58 -0
- package/dist/demo/demo-countdown.js +79 -0
- package/dist/demo/demo-countdown.js.map +1 -0
- package/dist/demo/demo-ended.d.ts +45 -0
- package/dist/demo/demo-ended.js +42 -0
- package/dist/demo/demo-ended.js.map +1 -0
- package/dist/demo/demo-labels.d.ts +53 -0
- package/dist/demo/demo-labels.js +29 -0
- package/dist/demo/demo-labels.js.map +1 -0
- package/dist/demo/demo-session.d.ts +30 -0
- package/dist/demo/demo-session.js +8 -0
- package/dist/demo/demo-session.js.map +1 -0
- package/dist/demo/demo-start.d.ts +54 -0
- package/dist/demo/demo-start.js +68 -0
- package/dist/demo/demo-start.js.map +1 -0
- package/dist/feedback/feedback-attachment.d.ts +5 -1
- package/dist/feedback/feedback-context.d.ts +5 -1
- package/dist/feedback/feedback-crash.d.ts +5 -1
- package/dist/feedback/feedback-dialog.d.ts +5 -1
- package/dist/feedback/feedback-inbox.d.ts +5 -1
- package/dist/feedback/feedback-inbox.js +12 -2
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-labels.d.ts +5 -1
- package/dist/feedback/feedback-labels.js +1 -0
- package/dist/feedback/feedback-labels.js.map +1 -1
- package/dist/feedback/feedback-menu.d.ts +5 -1
- package/dist/feedback/feedback-record.d.ts +5 -1
- package/dist/feedback/feedback-record.js +2 -0
- package/dist/feedback/feedback-record.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +5 -1
- package/dist/feedback/feedback-row-detail.js +7 -0
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +5 -1
- package/dist/feedback/feedback-submit.d.ts +5 -1
- package/dist/feedback/feedback-swipe.d.ts +6 -2
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +5 -1
- package/dist/feedback/feedback-thread.d.ts +5 -1
- package/dist/feedback.d.ts +5 -1
- package/dist/hooks/use-file-drop.d.ts +5 -1
- package/dist/i18n/defaults.d.ts +5 -1
- package/dist/i18n/defaults.js +6 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +5 -1
- package/dist/i18n/german.js +85 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -1
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +5 -1
- package/dist/i18n/locales/de-CH.d.ts +5 -1
- package/dist/i18n/locales/en.d.ts +5 -1
- package/dist/i18n/locales/es.d.ts +5 -1
- package/dist/i18n/locales/es.js +82 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +5 -1
- package/dist/i18n/locales/fr.js +83 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +5 -1
- package/dist/i18n/locales/hu.js +83 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +5 -1
- package/dist/i18n/locales/it.js +82 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +5 -1
- package/dist/i18n/locales/zh.js +81 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +5 -1
- package/dist/i18n/review.js +15 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +25 -3
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -1
- package/dist/landing/access.d.ts +48 -0
- package/dist/landing/access.js +38 -0
- package/dist/landing/access.js.map +1 -0
- package/dist/landing/landing-actions.d.ts +140 -0
- package/dist/landing/landing-actions.js +39 -0
- package/dist/landing/landing-actions.js.map +1 -0
- package/dist/landing/landing-labels.d.ts +43 -0
- package/dist/landing/landing-labels.js +23 -0
- package/dist/landing/landing-labels.js.map +1 -0
- package/dist/landing/landing-sections.d.ts +218 -0
- package/dist/landing/landing-sections.js +167 -0
- package/dist/landing/landing-sections.js.map +1 -0
- package/dist/landing/page-seo.d.ts +99 -0
- package/dist/landing/page-seo.js +121 -0
- package/dist/landing/page-seo.js.map +1 -0
- package/dist/landing/public-header.d.ts +148 -0
- package/dist/landing/public-header.js +49 -0
- package/dist/landing/public-header.js.map +1 -0
- package/dist/landing/routing.d.ts +231 -0
- package/dist/landing/routing.js +84 -0
- package/dist/landing/routing.js.map +1 -0
- package/dist/rhf/fields.d.ts +5 -1
- package/dist/rhf/form.d.ts +5 -1
- package/dist/rhf.d.ts +5 -1
- package/dist/settings/settings-catalogue.d.ts +84 -0
- package/dist/settings/settings-catalogue.js +47 -0
- package/dist/settings/settings-catalogue.js.map +1 -0
- package/dist/settings/settings-context.d.ts +31 -0
- package/dist/settings/settings-context.js +11 -0
- package/dist/settings/settings-context.js.map +1 -0
- package/dist/settings/settings-heading.d.ts +50 -0
- package/dist/settings/settings-heading.js +27 -0
- package/dist/settings/settings-heading.js.map +1 -0
- package/dist/settings/settings-labels.d.ts +48 -0
- package/dist/settings/settings-labels.js +43 -0
- package/dist/settings/settings-labels.js.map +1 -0
- package/dist/settings/settings-layout.d.ts +77 -0
- package/dist/settings/settings-layout.js +242 -0
- package/dist/settings/settings-layout.js.map +1 -0
- package/dist/settings/settings-section.d.ts +133 -0
- package/dist/settings/settings-section.js +63 -0
- package/dist/settings/settings-section.js.map +1 -0
- package/dist/settings/use-account-language.d.ts +83 -0
- package/dist/settings/use-account-language.js +62 -0
- package/dist/settings/use-account-language.js.map +1 -0
- package/dist/settings/use-settings-focus.d.ts +34 -0
- package/dist/settings/use-settings-focus.js +77 -0
- package/dist/settings/use-settings-focus.js.map +1 -0
- package/dist/settings/use-settings-route.d.ts +118 -0
- package/dist/settings/use-settings-route.js +120 -0
- package/dist/settings/use-settings-route.js.map +1 -0
- package/dist/shell/app-shell.d.ts +11 -1
- package/dist/shell/app-shell.js +3 -2
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/top-bar-brand.d.ts +5 -1
- package/dist/shell.d.ts +15 -1
- package/dist/shell.js +56 -0
- package/dist/shell.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +5 -1
- package/dist/wizard.d.ts +5 -1
- package/package.json +2 -1
- package/src/account/data-export-setting.tsx +3 -2
- package/src/account/delete-account-setting.tsx +3 -2
- package/src/account/email-change-setting.tsx +4 -7
- package/src/account/sessions-setting.tsx +3 -2
- package/src/admin/admin-parts.ts +9 -2
- package/src/admin/invitations-panel.tsx +11 -4
- package/src/auth/auth-errors.ts +36 -5
- package/src/auth/reset-password-form.tsx +6 -2
- package/src/auth/sign-in-form.tsx +34 -3
- package/src/auth/verify-email.tsx +14 -2
- package/src/components/account-settings-labels.ts +13 -0
- package/src/components/account-settings.tsx +33 -12
- package/src/components/confirm-dialog.tsx +20 -1
- package/src/components/data-table.tsx +8 -0
- package/src/components/date-picker.tsx +9 -4
- package/src/components/passkeys-setting.tsx +2 -1
- package/src/components/settings-fields.tsx +50 -1
- package/src/demo/demo-banner.tsx +159 -0
- package/src/demo/demo-countdown.ts +141 -0
- package/src/demo/demo-ended.tsx +89 -0
- package/src/demo/demo-labels.ts +78 -0
- package/src/demo/demo-session.ts +32 -0
- package/src/demo/demo-start.tsx +150 -0
- package/src/feedback/feedback-inbox.tsx +21 -8
- package/src/feedback/feedback-labels.ts +2 -1
- package/src/feedback/feedback-record.ts +10 -7
- package/src/feedback/feedback-row-detail.tsx +28 -4
- package/src/feedback/feedback-swipe.tsx +1 -1
- package/src/i18n/defaults.ts +6 -0
- package/src/i18n/german.ts +87 -0
- package/src/i18n/kit-labels.tsx +9 -0
- package/src/i18n/locales/es.ts +84 -0
- package/src/i18n/locales/fr.ts +85 -0
- package/src/i18n/locales/hu.ts +85 -0
- package/src/i18n/locales/it.ts +84 -0
- package/src/i18n/locales/zh.ts +82 -0
- package/src/i18n/review.ts +14 -0
- package/src/index.ts +98 -1
- package/src/landing/access.ts +97 -0
- package/src/landing/landing-actions.tsx +96 -0
- package/src/landing/landing-labels.ts +59 -0
- package/src/landing/landing-sections.tsx +337 -0
- package/src/landing/page-seo.ts +251 -0
- package/src/landing/public-header.tsx +120 -0
- package/src/landing/routing.tsx +226 -0
- package/src/settings/settings-catalogue.ts +132 -0
- package/src/settings/settings-context.ts +33 -0
- package/src/settings/settings-heading.tsx +65 -0
- package/src/settings/settings-labels.ts +87 -0
- package/src/settings/settings-layout.tsx +379 -0
- package/src/settings/settings-section.tsx +108 -0
- package/src/settings/use-account-language.ts +146 -0
- package/src/settings/use-settings-focus.ts +120 -0
- package/src/settings/use-settings-route.ts +258 -0
- package/src/shell/app-shell.tsx +10 -2
- package/src/shell.ts +51 -0
package/dist/rhf/form.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactElement, ComponentProps } from 'react';
|
|
3
3
|
import * as react_hook_form from 'react-hook-form';
|
|
4
4
|
import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
|
|
5
|
-
import { dW as LabelProps } from '../delete-account-setting-
|
|
5
|
+
import { dW as LabelProps } from '../delete-account-setting-CpIhzxxZ.js';
|
|
6
6
|
import '../components/tooltip.js';
|
|
7
7
|
import '../hooks/use-anchored-rect.js';
|
|
8
8
|
import '../lib/clipping.js';
|
|
@@ -57,6 +57,8 @@ import '../components/image-grid.js';
|
|
|
57
57
|
import '../components/lightbox.js';
|
|
58
58
|
import '../components/write-lock.js';
|
|
59
59
|
import '../auth/sign-in-form.js';
|
|
60
|
+
import '../landing/access.js';
|
|
61
|
+
import '../landing/landing-labels.js';
|
|
60
62
|
import '../shell/topbar-controls.js';
|
|
61
63
|
import '../theme/palette-presets.js';
|
|
62
64
|
import '../theme/chart-palette.js';
|
|
@@ -78,6 +80,8 @@ import '../account/data-export-setting.js';
|
|
|
78
80
|
import '../components/account-chips.js';
|
|
79
81
|
import '../admin/admin-parts.js';
|
|
80
82
|
import '../admin/admin-action-log.js';
|
|
83
|
+
import '../settings/settings-labels.js';
|
|
84
|
+
import '../demo/demo-labels.js';
|
|
81
85
|
import '../components/reauth-dialog.js';
|
|
82
86
|
import '../components/server-wake.js';
|
|
83
87
|
import '../lib/server-wake.js';
|
package/dist/rhf.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { RhfCheckbox, RhfCheckboxProps, RhfCombobox, RhfComboboxProps, RhfCountr
|
|
|
4
4
|
export { RhfLineItemCellContext, RhfLineItems, RhfLineItemsColumn, RhfLineItemsProps } from './rhf/line-items.js';
|
|
5
5
|
import 'react';
|
|
6
6
|
import 'react-hook-form';
|
|
7
|
-
import './delete-account-setting-
|
|
7
|
+
import './delete-account-setting-CpIhzxxZ.js';
|
|
8
8
|
import './components/tooltip.js';
|
|
9
9
|
import './hooks/use-anchored-rect.js';
|
|
10
10
|
import './lib/clipping.js';
|
|
@@ -59,6 +59,8 @@ import './components/image-grid.js';
|
|
|
59
59
|
import './components/lightbox.js';
|
|
60
60
|
import './components/write-lock.js';
|
|
61
61
|
import './auth/sign-in-form.js';
|
|
62
|
+
import './landing/access.js';
|
|
63
|
+
import './landing/landing-labels.js';
|
|
62
64
|
import './shell/topbar-controls.js';
|
|
63
65
|
import './theme/palette-presets.js';
|
|
64
66
|
import './theme/chart-palette.js';
|
|
@@ -80,6 +82,8 @@ import './account/data-export-setting.js';
|
|
|
80
82
|
import './components/account-chips.js';
|
|
81
83
|
import './admin/admin-parts.js';
|
|
82
84
|
import './admin/admin-action-log.js';
|
|
85
|
+
import './settings/settings-labels.js';
|
|
86
|
+
import './demo/demo-labels.js';
|
|
83
87
|
import './components/reauth-dialog.js';
|
|
84
88
|
import './components/server-wake.js';
|
|
85
89
|
import './lib/server-wake.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { SearchEntry } from '../search/search-index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The settings catalogue (docs/settings-harmonization.md §4.1), lifted from keksdose's
|
|
6
|
+
* `settings-index.ts`: the groups a settings page is split into, and one entry per
|
|
7
|
+
* SETTING — not per card — so a search for "quiet hours" finds the field inside the
|
|
8
|
+
* notifications card. One list feeds the sidebar, the phone list, the in-page search and
|
|
9
|
+
* ⌘K ({@link settingsSearchEntries}); keksdose's page and its palette drifted when they
|
|
10
|
+
* had one each.
|
|
11
|
+
*
|
|
12
|
+
* The kit resolves no words: `title`, `help` and `keywords` arrive translated (an app's
|
|
13
|
+
* own, or the core groups' from `DEFAULT_SETTINGS_LABELS.groups`).
|
|
14
|
+
*/
|
|
15
|
+
/** One group of the page: a sidebar row on a desktop, a list row on a phone (§3.2, §3.3). */
|
|
16
|
+
interface SettingsGroup<G extends string = string> {
|
|
17
|
+
/** The path segment, `/settings/<id>`: lowercase, a–z and "-". */
|
|
18
|
+
id: G;
|
|
19
|
+
/** The row's icon — a lucide icon at the row's size is sized by the row. */
|
|
20
|
+
icon: ReactNode;
|
|
21
|
+
/** The group's name: the sidebar row, the heading over its cards, the phone list. */
|
|
22
|
+
title: string;
|
|
23
|
+
/** One line under the heading and in the phone list, where it is cut to one line. */
|
|
24
|
+
help: string;
|
|
25
|
+
/**
|
|
26
|
+
* `false` hides the group: no row, no hits, and its path acts as an unknown one — it
|
|
27
|
+
* lands on the default group (desktop) or the list (phone), never on a 404 (§3.1).
|
|
28
|
+
* kastlan's `/admin/billing` while billing is off. Default: visible.
|
|
29
|
+
*/
|
|
30
|
+
visible?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** One setting, and the card that holds it (§4.1). */
|
|
33
|
+
interface SettingsEntry<G extends string = string> {
|
|
34
|
+
/** Unique within the catalogue. */
|
|
35
|
+
id: string;
|
|
36
|
+
/** The group whose page shows the card. */
|
|
37
|
+
group: G;
|
|
38
|
+
/** The card's DOM id, unique on the page — the `?focus=` target. Several entries may
|
|
39
|
+
* share one: a card that holds four settings has four entries. */
|
|
40
|
+
anchor: string;
|
|
41
|
+
/** What a search hit says. */
|
|
42
|
+
title: string;
|
|
43
|
+
/** Localized, space-separated words people type that the title does not contain
|
|
44
|
+
* ("dark night" for the theme, "2fa totp" for two-factor). Never shown. */
|
|
45
|
+
keywords?: string;
|
|
46
|
+
/**
|
|
47
|
+
* `false` drops the entry from the page's search and ⌘K. A group whose entries are
|
|
48
|
+
* ALL hidden is hidden with them (§4.1): a demo session marks every card it is refused
|
|
49
|
+
* (passkeys, 2FA, export, deletion …) and so never sees a page of cards that each
|
|
50
|
+
* answer 403. Default: visible.
|
|
51
|
+
*/
|
|
52
|
+
visible?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The groups that show, in their order: not `visible: false`, and — when `entries` is
|
|
56
|
+
* given — holding at least one visible entry (§4.1: Kurvenschmiede's notifications group
|
|
57
|
+
* stays hidden until it has a card). Without `entries` only the groups' own flag counts.
|
|
58
|
+
*/
|
|
59
|
+
declare function visibleSettingsGroups<G extends string>(groups: readonly SettingsGroup<G>[], entries?: readonly SettingsEntry<G>[]): SettingsGroup<G>[];
|
|
60
|
+
/** The visible entries of the visible groups — what search may find. */
|
|
61
|
+
declare function visibleSettingsEntries<G extends string>(groups: readonly SettingsGroup<G>[], entries: readonly SettingsEntry<G>[]): SettingsEntry<G>[];
|
|
62
|
+
/** `basePath` without a trailing slash: "/settings/" and "/settings" are one page. */
|
|
63
|
+
declare function normalizeSettingsBase(basePath: string): string;
|
|
64
|
+
/** `/settings/<group>`, and `?focus=<anchor>` when a card is named (§3.4). */
|
|
65
|
+
declare function settingsHref(basePath: string, group: string, anchor?: string): string;
|
|
66
|
+
/** "dark night" → ["dark", "night"]. */
|
|
67
|
+
declare function settingsKeywords(keywords: string | undefined): string[];
|
|
68
|
+
interface SettingsSearchEntriesOptions {
|
|
69
|
+
/** `/settings` or `/admin` — the hits link to `<basePath>/<group>?focus=<anchor>`. */
|
|
70
|
+
basePath: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The catalogue as {@link GlobalSearch} entries (§3.4): each visible setting, under its
|
|
74
|
+
* group's name as the palette's section, linking to `/settings/<group>?focus=<anchor>` —
|
|
75
|
+
* the same link the page's own search hit is, so ⌘K, a tour and a mail all land the
|
|
76
|
+
* same way (§3.5). The group's name is a keyword too, as on the page: "security" lists
|
|
77
|
+
* the security group's settings.
|
|
78
|
+
*
|
|
79
|
+
* Ids are prefixed with the base path ("settings:theme", "admin:users") so a palette
|
|
80
|
+
* that also holds pages and actions keeps them unique.
|
|
81
|
+
*/
|
|
82
|
+
declare function settingsSearchEntries<G extends string>(groups: readonly SettingsGroup<G>[], entries: readonly SettingsEntry<G>[], options: SettingsSearchEntriesOptions): SearchEntry[];
|
|
83
|
+
|
|
84
|
+
export { type SettingsEntry, type SettingsGroup, type SettingsSearchEntriesOptions, normalizeSettingsBase, settingsHref, settingsKeywords, settingsSearchEntries, visibleSettingsEntries, visibleSettingsGroups };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
function visibleSettingsGroups(groups, entries) {
|
|
3
|
+
if (!entries) return groups.filter((g) => g.visible !== false);
|
|
4
|
+
const filled = /* @__PURE__ */ new Set();
|
|
5
|
+
for (const e of entries) if (e.visible !== false) filled.add(e.group);
|
|
6
|
+
return groups.filter((g) => g.visible !== false && filled.has(g.id));
|
|
7
|
+
}
|
|
8
|
+
function visibleSettingsEntries(groups, entries) {
|
|
9
|
+
const shown = new Set(visibleSettingsGroups(groups, entries).map((g) => g.id));
|
|
10
|
+
return entries.filter((e) => e.visible !== false && shown.has(e.group));
|
|
11
|
+
}
|
|
12
|
+
function normalizeSettingsBase(basePath) {
|
|
13
|
+
const trimmed = basePath.replace(/\/+$/, "");
|
|
14
|
+
return trimmed.startsWith("/") || trimmed === "" ? trimmed : `/${trimmed}`;
|
|
15
|
+
}
|
|
16
|
+
function settingsHref(basePath, group, anchor) {
|
|
17
|
+
const path = `${normalizeSettingsBase(basePath)}/${group}`;
|
|
18
|
+
return anchor ? `${path}?focus=${encodeURIComponent(anchor)}` : path;
|
|
19
|
+
}
|
|
20
|
+
function settingsKeywords(keywords) {
|
|
21
|
+
return keywords ? keywords.split(/\s+/).filter(Boolean) : [];
|
|
22
|
+
}
|
|
23
|
+
function settingsSearchEntries(groups, entries, options) {
|
|
24
|
+
const base = normalizeSettingsBase(options.basePath);
|
|
25
|
+
const prefix = base.replace(/^\//, "").replace(/\//g, ":") || "settings";
|
|
26
|
+
const byId = new Map(visibleSettingsGroups(groups, entries).map((g) => [g.id, g]));
|
|
27
|
+
return visibleSettingsEntries(groups, entries).map((e) => {
|
|
28
|
+
const group = byId.get(e.group);
|
|
29
|
+
return {
|
|
30
|
+
id: `${prefix}:${e.id}`,
|
|
31
|
+
title: e.title,
|
|
32
|
+
keywords: [group.title, ...settingsKeywords(e.keywords)],
|
|
33
|
+
group: group.title,
|
|
34
|
+
icon: group.icon,
|
|
35
|
+
href: settingsHref(base, e.group, e.anchor)
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
normalizeSettingsBase,
|
|
41
|
+
settingsHref,
|
|
42
|
+
settingsKeywords,
|
|
43
|
+
settingsSearchEntries,
|
|
44
|
+
visibleSettingsEntries,
|
|
45
|
+
visibleSettingsGroups
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=settings-catalogue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/settings/settings-catalogue.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { SearchEntry } from \"../search/search-index\";\n\n/**\n * The settings catalogue (docs/settings-harmonization.md §4.1), lifted from keksdose's\n * `settings-index.ts`: the groups a settings page is split into, and one entry per\n * SETTING — not per card — so a search for \"quiet hours\" finds the field inside the\n * notifications card. One list feeds the sidebar, the phone list, the in-page search and\n * ⌘K ({@link settingsSearchEntries}); keksdose's page and its palette drifted when they\n * had one each.\n *\n * The kit resolves no words: `title`, `help` and `keywords` arrive translated (an app's\n * own, or the core groups' from `DEFAULT_SETTINGS_LABELS.groups`).\n */\n\n/** One group of the page: a sidebar row on a desktop, a list row on a phone (§3.2, §3.3). */\nexport interface SettingsGroup<G extends string = string> {\n /** The path segment, `/settings/<id>`: lowercase, a–z and \"-\". */\n id: G;\n /** The row's icon — a lucide icon at the row's size is sized by the row. */\n icon: ReactNode;\n /** The group's name: the sidebar row, the heading over its cards, the phone list. */\n title: string;\n /** One line under the heading and in the phone list, where it is cut to one line. */\n help: string;\n /**\n * `false` hides the group: no row, no hits, and its path acts as an unknown one — it\n * lands on the default group (desktop) or the list (phone), never on a 404 (§3.1).\n * kastlan's `/admin/billing` while billing is off. Default: visible.\n */\n visible?: boolean;\n}\n\n/** One setting, and the card that holds it (§4.1). */\nexport interface SettingsEntry<G extends string = string> {\n /** Unique within the catalogue. */\n id: string;\n /** The group whose page shows the card. */\n group: G;\n /** The card's DOM id, unique on the page — the `?focus=` target. Several entries may\n * share one: a card that holds four settings has four entries. */\n anchor: string;\n /** What a search hit says. */\n title: string;\n /** Localized, space-separated words people type that the title does not contain\n * (\"dark night\" for the theme, \"2fa totp\" for two-factor). Never shown. */\n keywords?: string;\n /**\n * `false` drops the entry from the page's search and ⌘K. A group whose entries are\n * ALL hidden is hidden with them (§4.1): a demo session marks every card it is refused\n * (passkeys, 2FA, export, deletion …) and so never sees a page of cards that each\n * answer 403. Default: visible.\n */\n visible?: boolean;\n}\n\n/**\n * The groups that show, in their order: not `visible: false`, and — when `entries` is\n * given — holding at least one visible entry (§4.1: Kurvenschmiede's notifications group\n * stays hidden until it has a card). Without `entries` only the groups' own flag counts.\n */\nexport function visibleSettingsGroups<G extends string>(\n groups: readonly SettingsGroup<G>[],\n entries?: readonly SettingsEntry<G>[],\n): SettingsGroup<G>[] {\n if (!entries) return groups.filter((g) => g.visible !== false);\n const filled = new Set<G>();\n for (const e of entries) if (e.visible !== false) filled.add(e.group);\n return groups.filter((g) => g.visible !== false && filled.has(g.id));\n}\n\n/** The visible entries of the visible groups — what search may find. */\nexport function visibleSettingsEntries<G extends string>(\n groups: readonly SettingsGroup<G>[],\n entries: readonly SettingsEntry<G>[],\n): SettingsEntry<G>[] {\n const shown = new Set(visibleSettingsGroups(groups, entries).map((g) => g.id));\n return entries.filter((e) => e.visible !== false && shown.has(e.group));\n}\n\n/** `basePath` without a trailing slash: \"/settings/\" and \"/settings\" are one page. */\nexport function normalizeSettingsBase(basePath: string): string {\n const trimmed = basePath.replace(/\\/+$/, \"\");\n return trimmed.startsWith(\"/\") || trimmed === \"\" ? trimmed : `/${trimmed}`;\n}\n\n/** `/settings/<group>`, and `?focus=<anchor>` when a card is named (§3.4). */\nexport function settingsHref(basePath: string, group: string, anchor?: string): string {\n const path = `${normalizeSettingsBase(basePath)}/${group}`;\n return anchor ? `${path}?focus=${encodeURIComponent(anchor)}` : path;\n}\n\n/** \"dark night\" → [\"dark\", \"night\"]. */\nexport function settingsKeywords(keywords: string | undefined): string[] {\n return keywords ? keywords.split(/\\s+/).filter(Boolean) : [];\n}\n\nexport interface SettingsSearchEntriesOptions {\n /** `/settings` or `/admin` — the hits link to `<basePath>/<group>?focus=<anchor>`. */\n basePath: string;\n}\n\n/**\n * The catalogue as {@link GlobalSearch} entries (§3.4): each visible setting, under its\n * group's name as the palette's section, linking to `/settings/<group>?focus=<anchor>` —\n * the same link the page's own search hit is, so ⌘K, a tour and a mail all land the\n * same way (§3.5). The group's name is a keyword too, as on the page: \"security\" lists\n * the security group's settings.\n *\n * Ids are prefixed with the base path (\"settings:theme\", \"admin:users\") so a palette\n * that also holds pages and actions keeps them unique.\n */\nexport function settingsSearchEntries<G extends string>(\n groups: readonly SettingsGroup<G>[],\n entries: readonly SettingsEntry<G>[],\n options: SettingsSearchEntriesOptions,\n): SearchEntry[] {\n const base = normalizeSettingsBase(options.basePath);\n const prefix = base.replace(/^\\//, \"\").replace(/\\//g, \":\") || \"settings\";\n const byId = new Map(visibleSettingsGroups(groups, entries).map((g) => [g.id, g]));\n return visibleSettingsEntries(groups, entries).map((e) => {\n const group = byId.get(e.group)!;\n return {\n id: `${prefix}:${e.id}`,\n title: e.title,\n keywords: [group.title, ...settingsKeywords(e.keywords)],\n group: group.title,\n icon: group.icon,\n href: settingsHref(base, e.group, e.anchor),\n };\n });\n}\n"],"mappings":";AA6DO,SAAS,sBACd,QACA,SACoB;AACpB,MAAI,CAAC,QAAS,QAAO,OAAO,OAAO,CAAC,MAAM,EAAE,YAAY,KAAK;AAC7D,QAAM,SAAS,oBAAI,IAAO;AAC1B,aAAW,KAAK,QAAS,KAAI,EAAE,YAAY,MAAO,QAAO,IAAI,EAAE,KAAK;AACpE,SAAO,OAAO,OAAO,CAAC,MAAM,EAAE,YAAY,SAAS,OAAO,IAAI,EAAE,EAAE,CAAC;AACrE;AAGO,SAAS,uBACd,QACA,SACoB;AACpB,QAAM,QAAQ,IAAI,IAAI,sBAAsB,QAAQ,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC7E,SAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY,SAAS,MAAM,IAAI,EAAE,KAAK,CAAC;AACxE;AAGO,SAAS,sBAAsB,UAA0B;AAC9D,QAAM,UAAU,SAAS,QAAQ,QAAQ,EAAE;AAC3C,SAAO,QAAQ,WAAW,GAAG,KAAK,YAAY,KAAK,UAAU,IAAI,OAAO;AAC1E;AAGO,SAAS,aAAa,UAAkB,OAAe,QAAyB;AACrF,QAAM,OAAO,GAAG,sBAAsB,QAAQ,CAAC,IAAI,KAAK;AACxD,SAAO,SAAS,GAAG,IAAI,UAAU,mBAAmB,MAAM,CAAC,KAAK;AAClE;AAGO,SAAS,iBAAiB,UAAwC;AACvE,SAAO,WAAW,SAAS,MAAM,KAAK,EAAE,OAAO,OAAO,IAAI,CAAC;AAC7D;AAiBO,SAAS,sBACd,QACA,SACA,SACe;AACf,QAAM,OAAO,sBAAsB,QAAQ,QAAQ;AACnD,QAAM,SAAS,KAAK,QAAQ,OAAO,EAAE,EAAE,QAAQ,OAAO,GAAG,KAAK;AAC9D,QAAM,OAAO,IAAI,IAAI,sBAAsB,QAAQ,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AACjF,SAAO,uBAAuB,QAAQ,OAAO,EAAE,IAAI,CAAC,MAAM;AACxD,UAAM,QAAQ,KAAK,IAAI,EAAE,KAAK;AAC9B,WAAO;AAAA,MACL,IAAI,GAAG,MAAM,IAAI,EAAE,EAAE;AAAA,MACrB,OAAO,EAAE;AAAA,MACT,UAAU,CAAC,MAAM,OAAO,GAAG,iBAAiB,EAAE,QAAQ,CAAC;AAAA,MACvD,OAAO,MAAM;AAAA,MACb,MAAM,MAAM;AAAA,MACZ,MAAM,aAAa,MAAM,EAAE,OAAO,EAAE,MAAM;AAAA,IAC5C;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { SettingsHeadingTag } from './settings-heading.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What {@link SettingsLayout} hands the cards it renders (docs/settings-harmonization.md
|
|
6
|
+
* §3.1, §3.7). Router-free on purpose, so `SettingsSection` and an app's cards can read
|
|
7
|
+
* it without importing the layout.
|
|
8
|
+
*/
|
|
9
|
+
interface SettingsLayoutContextValue {
|
|
10
|
+
/** `/settings` or `/admin`, without a trailing slash. */
|
|
11
|
+
basePath: string;
|
|
12
|
+
/** The group on screen; `null` on a phone's list. */
|
|
13
|
+
group: string | null;
|
|
14
|
+
/** The path segment after the group — the surface a card owns (keksdose's swipe card). */
|
|
15
|
+
sub: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Select the card's sub-surface: `/settings/<group>/<sub>`, or the group alone with
|
|
18
|
+
* `null`. A replace, keeping the query and the history marker: switching a tab inside a
|
|
19
|
+
* card is not a place to go back to. The card never builds the path itself (§3.1).
|
|
20
|
+
*/
|
|
21
|
+
selectSub: (sub: string | null) => void;
|
|
22
|
+
/** The level the cards' titles are at: `h3` on a desktop, `h2` on a phone. */
|
|
23
|
+
headingLevel: SettingsHeadingTag;
|
|
24
|
+
/** Whether the catalogue has an entry with this anchor — `SettingsSection`'s check. */
|
|
25
|
+
hasAnchor: (anchor: string) => boolean;
|
|
26
|
+
}
|
|
27
|
+
declare const SettingsLayoutContext: react.Context<SettingsLayoutContextValue | null>;
|
|
28
|
+
/** The {@link SettingsLayout} around this card, or `null` outside one. */
|
|
29
|
+
declare function useSettingsLayout(): SettingsLayoutContextValue | null;
|
|
30
|
+
|
|
31
|
+
export { SettingsLayoutContext, type SettingsLayoutContextValue, useSettingsLayout };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const SettingsLayoutContext = createContext(null);
|
|
4
|
+
function useSettingsLayout() {
|
|
5
|
+
return useContext(SettingsLayoutContext);
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
SettingsLayoutContext,
|
|
9
|
+
useSettingsLayout
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=settings-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/settings/settings-context.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { SettingsHeadingTag } from \"./settings-heading\";\n\n/**\n * What {@link SettingsLayout} hands the cards it renders (docs/settings-harmonization.md\n * §3.1, §3.7). Router-free on purpose, so `SettingsSection` and an app's cards can read\n * it without importing the layout.\n */\nexport interface SettingsLayoutContextValue {\n /** `/settings` or `/admin`, without a trailing slash. */\n basePath: string;\n /** The group on screen; `null` on a phone's list. */\n group: string | null;\n /** The path segment after the group — the surface a card owns (keksdose's swipe card). */\n sub: string | null;\n /**\n * Select the card's sub-surface: `/settings/<group>/<sub>`, or the group alone with\n * `null`. A replace, keeping the query and the history marker: switching a tab inside a\n * card is not a place to go back to. The card never builds the path itself (§3.1).\n */\n selectSub: (sub: string | null) => void;\n /** The level the cards' titles are at: `h3` on a desktop, `h2` on a phone. */\n headingLevel: SettingsHeadingTag;\n /** Whether the catalogue has an entry with this anchor — `SettingsSection`'s check. */\n hasAnchor: (anchor: string) => boolean;\n}\n\nexport const SettingsLayoutContext = createContext<SettingsLayoutContextValue | null>(null);\n\n/** The {@link SettingsLayout} around this card, or `null` outside one. */\nexport function useSettingsLayout(): SettingsLayoutContextValue | null {\n return useContext(SettingsLayoutContext);\n}\n"],"mappings":";AAAA,SAAS,eAAe,kBAAkB;AA2BnC,MAAM,wBAAwB,cAAiD,IAAI;AAGnF,SAAS,oBAAuD;AACrE,SAAO,WAAW,qBAAqB;AACzC;","names":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
|
|
4
|
+
/** The heading a settings card's title becomes. */
|
|
5
|
+
type SettingsHeadingTag = "h2" | "h3" | "h4";
|
|
6
|
+
interface SettingsHeadingLevelProps {
|
|
7
|
+
/** The element every kit setting card below titles itself with. */
|
|
8
|
+
level: SettingsHeadingTag;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The level the kit's setting cards title themselves at (docs/settings-harmonization.md
|
|
13
|
+
* §3.7). Inside {@link SettingsLayout} a card's title is a heading under the group's —
|
|
14
|
+
* `h3` under the desktop's `h2`, `h2` under a phone group page's `h1` — and the layout
|
|
15
|
+
* sets this for every card it renders. Outside one, nothing is set and the same cards
|
|
16
|
+
* keep the plain `div` title they always had, so a card on an app's own page does not
|
|
17
|
+
* suddenly put an `h3` into an outline that has no `h2`.
|
|
18
|
+
*
|
|
19
|
+
* Set it yourself only where an app lays out settings cards without the layout and
|
|
20
|
+
* wants them in its outline: `<SettingsHeadingLevel level="h3">…</SettingsHeadingLevel>`.
|
|
21
|
+
* `ProfileSetting`, `PasswordSetting`, `TwoFactorSetting`, `PasskeysSetting`,
|
|
22
|
+
* `EmailChangeSetting`, `SessionsSetting`, `DataExportSetting`, `DeleteAccountSetting`
|
|
23
|
+
* and `SettingsSection` read it.
|
|
24
|
+
*/
|
|
25
|
+
declare function SettingsHeadingLevel({ level, children }: SettingsHeadingLevelProps): react.JSX.Element;
|
|
26
|
+
/** The nearest {@link SettingsHeadingLevel}, or `null` outside one. */
|
|
27
|
+
declare function useSettingsHeadingLevel(): SettingsHeadingTag | null;
|
|
28
|
+
/**
|
|
29
|
+
* A setting card's title at the settings type scale: a heading inside a
|
|
30
|
+
* {@link SettingsHeadingLevel}, the plain `div` outside one.
|
|
31
|
+
*
|
|
32
|
+
* As a heading it takes `tabIndex={-1}`: `?focus=` moves focus to the card's heading so a
|
|
33
|
+
* screen reader lands on the card it was sent to (§3.5), and a heading is not focusable
|
|
34
|
+
* otherwise. No outline of its own — the ring round the card marks it.
|
|
35
|
+
*
|
|
36
|
+
* @internal The kit's cards use it; not part of the barrel.
|
|
37
|
+
*/
|
|
38
|
+
declare function SettingsCardTitle({ className, ...props }: ComponentPropsWithoutRef<"div">): react.JSX.Element;
|
|
39
|
+
/**
|
|
40
|
+
* A heading at a level chosen at run time. Its own component, taking the tag as a prop
|
|
41
|
+
* (as `CardTitle` does), so the element type is a prop and not a value computed in the
|
|
42
|
+
* caller's render.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
declare function SettingsHeading({ as: Tag, ...props }: {
|
|
47
|
+
as: SettingsHeadingTag;
|
|
48
|
+
} & ComponentPropsWithoutRef<"h2">): react.JSX.Element;
|
|
49
|
+
|
|
50
|
+
export { SettingsCardTitle, SettingsHeading, SettingsHeadingLevel, type SettingsHeadingLevelProps, type SettingsHeadingTag, useSettingsHeadingLevel };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { CARD_TITLE_CLASS } from "../account/account-parts.js";
|
|
6
|
+
const SettingsHeadingLevelContext = createContext(null);
|
|
7
|
+
function SettingsHeadingLevel({ level, children }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(SettingsHeadingLevelContext.Provider, { value: level, children });
|
|
9
|
+
}
|
|
10
|
+
function useSettingsHeadingLevel() {
|
|
11
|
+
return useContext(SettingsHeadingLevelContext);
|
|
12
|
+
}
|
|
13
|
+
function SettingsCardTitle({ className, ...props }) {
|
|
14
|
+
const level = useSettingsHeadingLevel();
|
|
15
|
+
if (!level) return /* @__PURE__ */ jsx("div", { ...props, className: cn(CARD_TITLE_CLASS, className) });
|
|
16
|
+
return /* @__PURE__ */ jsx(SettingsHeading, { as: level, tabIndex: -1, ...props, className: cn(CARD_TITLE_CLASS, "outline-none", className) });
|
|
17
|
+
}
|
|
18
|
+
function SettingsHeading({ as: Tag, ...props }) {
|
|
19
|
+
return /* @__PURE__ */ jsx(Tag, { ...props });
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
SettingsCardTitle,
|
|
23
|
+
SettingsHeading,
|
|
24
|
+
SettingsHeadingLevel,
|
|
25
|
+
useSettingsHeadingLevel
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=settings-heading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/settings/settings-heading.tsx"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { CARD_TITLE_CLASS } from \"../account/account-parts\";\n\n/** The heading a settings card's title becomes. */\nexport type SettingsHeadingTag = \"h2\" | \"h3\" | \"h4\";\n\nconst SettingsHeadingLevelContext = createContext<SettingsHeadingTag | null>(null);\n\nexport interface SettingsHeadingLevelProps {\n /** The element every kit setting card below titles itself with. */\n level: SettingsHeadingTag;\n children?: ReactNode;\n}\n\n/**\n * The level the kit's setting cards title themselves at (docs/settings-harmonization.md\n * §3.7). Inside {@link SettingsLayout} a card's title is a heading under the group's —\n * `h3` under the desktop's `h2`, `h2` under a phone group page's `h1` — and the layout\n * sets this for every card it renders. Outside one, nothing is set and the same cards\n * keep the plain `div` title they always had, so a card on an app's own page does not\n * suddenly put an `h3` into an outline that has no `h2`.\n *\n * Set it yourself only where an app lays out settings cards without the layout and\n * wants them in its outline: `<SettingsHeadingLevel level=\"h3\">…</SettingsHeadingLevel>`.\n * `ProfileSetting`, `PasswordSetting`, `TwoFactorSetting`, `PasskeysSetting`,\n * `EmailChangeSetting`, `SessionsSetting`, `DataExportSetting`, `DeleteAccountSetting`\n * and `SettingsSection` read it.\n */\nexport function SettingsHeadingLevel({ level, children }: SettingsHeadingLevelProps) {\n return <SettingsHeadingLevelContext.Provider value={level}>{children}</SettingsHeadingLevelContext.Provider>;\n}\n\n/** The nearest {@link SettingsHeadingLevel}, or `null` outside one. */\nexport function useSettingsHeadingLevel(): SettingsHeadingTag | null {\n return useContext(SettingsHeadingLevelContext);\n}\n\n/**\n * A setting card's title at the settings type scale: a heading inside a\n * {@link SettingsHeadingLevel}, the plain `div` outside one.\n *\n * As a heading it takes `tabIndex={-1}`: `?focus=` moves focus to the card's heading so a\n * screen reader lands on the card it was sent to (§3.5), and a heading is not focusable\n * otherwise. No outline of its own — the ring round the card marks it.\n *\n * @internal The kit's cards use it; not part of the barrel.\n */\nexport function SettingsCardTitle({ className, ...props }: ComponentPropsWithoutRef<\"div\">) {\n const level = useSettingsHeadingLevel();\n if (!level) return <div {...props} className={cn(CARD_TITLE_CLASS, className)} />;\n return <SettingsHeading as={level} tabIndex={-1} {...props} className={cn(CARD_TITLE_CLASS, \"outline-none\", className)} />;\n}\n\n/**\n * A heading at a level chosen at run time. Its own component, taking the tag as a prop\n * (as `CardTitle` does), so the element type is a prop and not a value computed in the\n * caller's render.\n *\n * @internal\n */\nexport function SettingsHeading({ as: Tag, ...props }: { as: SettingsHeadingTag } & ComponentPropsWithoutRef<\"h2\">) {\n return <Tag {...props} />;\n}\n"],"mappings":";AA+BS;AA/BT,SAAS,eAAe,kBAAkB;AAE1C,SAAS,UAAU;AACnB,SAAS,wBAAwB;AAKjC,MAAM,8BAA8B,cAAyC,IAAI;AAsB1E,SAAS,qBAAqB,EAAE,OAAO,SAAS,GAA8B;AACnF,SAAO,oBAAC,4BAA4B,UAA5B,EAAqC,OAAO,OAAQ,UAAS;AACvE;AAGO,SAAS,0BAAqD;AACnE,SAAO,WAAW,2BAA2B;AAC/C;AAYO,SAAS,kBAAkB,EAAE,WAAW,GAAG,MAAM,GAAoC;AAC1F,QAAM,QAAQ,wBAAwB;AACtC,MAAI,CAAC,MAAO,QAAO,oBAAC,SAAK,GAAG,OAAO,WAAW,GAAG,kBAAkB,SAAS,GAAG;AAC/E,SAAO,oBAAC,mBAAgB,IAAI,OAAO,UAAU,IAAK,GAAG,OAAO,WAAW,GAAG,kBAAkB,gBAAgB,SAAS,GAAG;AAC1H;AASO,SAAS,gBAAgB,EAAE,IAAI,KAAK,GAAG,MAAM,GAAgE;AAClH,SAAO,oBAAC,OAAK,GAAG,OAAO;AACzB;","names":[]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `settings` namespace (docs/settings-harmonization.md §4.3): the settings page's
|
|
3
|
+
* own words, and the names and help lines of the five core groups every app shares —
|
|
4
|
+
* appearance, account, security, notifications, data (§2.6). An app's own groups bring
|
|
5
|
+
* their own words; any core label may be overridden like any kit label (prop >
|
|
6
|
+
* `<UiKitProvider labels={{ settings }}>` > English).
|
|
7
|
+
*/
|
|
8
|
+
/** A core group's two strings, as {@link SettingsGroup} takes them. */
|
|
9
|
+
interface SettingsGroupLabels {
|
|
10
|
+
/** The group's name. */
|
|
11
|
+
title: string;
|
|
12
|
+
/** One line: under the heading, and in the phone list. */
|
|
13
|
+
help: string;
|
|
14
|
+
}
|
|
15
|
+
/** The five groups whose words the kit ships (§2.6, §4.2). */
|
|
16
|
+
type SettingsCoreGroup = "appearance" | "account" | "security" | "notifications" | "data";
|
|
17
|
+
interface SettingsLabels {
|
|
18
|
+
/** The page's heading, and the back link's visible text on a phone's group page. */
|
|
19
|
+
title: string;
|
|
20
|
+
/** The search field's accessible name. */
|
|
21
|
+
search: string;
|
|
22
|
+
/** The search field's placeholder. */
|
|
23
|
+
searchPlaceholder: string;
|
|
24
|
+
/** The hit list's accessible name. */
|
|
25
|
+
results: string;
|
|
26
|
+
/** No hits for the trimmed query (§3.4). */
|
|
27
|
+
noMatches: (query: string) => string;
|
|
28
|
+
/** The button beside it that empties the field. */
|
|
29
|
+
clearSearch: string;
|
|
30
|
+
/** The phone's back link's accessible name: "‹ Settings" says where, this says what. */
|
|
31
|
+
back: string;
|
|
32
|
+
/** The sidebar's accessible name (the vertical tablist). */
|
|
33
|
+
sections: string;
|
|
34
|
+
/** A sidebar row's hit badge, read in place of the bare number while searching. */
|
|
35
|
+
matchCount: (count: number) => string;
|
|
36
|
+
/** The core groups' names and help lines. */
|
|
37
|
+
groups: Record<SettingsCoreGroup, SettingsGroupLabels>;
|
|
38
|
+
}
|
|
39
|
+
declare const DEFAULT_SETTINGS_LABELS: SettingsLabels;
|
|
40
|
+
/**
|
|
41
|
+
* 0.31.1: the `settings` namespace, resolved — English, then the provider, then `labels`.
|
|
42
|
+
* What an app reads for its catalogue's core group titles and help lines, and for the
|
|
43
|
+
* page title "<group> · Settings · <app>" (settings contract §3.1), without restating
|
|
44
|
+
* the namespace and its defaults (Kurvenschmiede's 0.31 adoption).
|
|
45
|
+
*/
|
|
46
|
+
declare function useSettingsLabels(labels?: Partial<SettingsLabels>): SettingsLabels;
|
|
47
|
+
|
|
48
|
+
export { DEFAULT_SETTINGS_LABELS, type SettingsCoreGroup, type SettingsGroupLabels, type SettingsLabels, useSettingsLabels };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
3
|
+
const DEFAULT_SETTINGS_LABELS = {
|
|
4
|
+
title: "Settings",
|
|
5
|
+
search: "Search settings",
|
|
6
|
+
searchPlaceholder: "Search settings\u2026",
|
|
7
|
+
results: "Matching settings",
|
|
8
|
+
noMatches: (query) => `No settings match \u201C${query}\u201D.`,
|
|
9
|
+
clearSearch: "Clear search",
|
|
10
|
+
back: "Back to settings",
|
|
11
|
+
sections: "Settings sections",
|
|
12
|
+
matchCount: (count) => count === 1 ? "1 match" : `${count} matches`,
|
|
13
|
+
groups: {
|
|
14
|
+
appearance: {
|
|
15
|
+
title: "Appearance",
|
|
16
|
+
help: "Language, theme and how the app looks on this device."
|
|
17
|
+
},
|
|
18
|
+
account: {
|
|
19
|
+
title: "Account",
|
|
20
|
+
help: "Your profile and your email address."
|
|
21
|
+
},
|
|
22
|
+
security: {
|
|
23
|
+
title: "Security",
|
|
24
|
+
help: "How you sign in, and where you are signed in."
|
|
25
|
+
},
|
|
26
|
+
notifications: {
|
|
27
|
+
title: "Notifications",
|
|
28
|
+
help: "What you are told about, when, and on which devices."
|
|
29
|
+
},
|
|
30
|
+
data: {
|
|
31
|
+
title: "Data",
|
|
32
|
+
help: "Export what is yours, or delete it together with your account."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function useSettingsLabels(labels) {
|
|
37
|
+
return useKitLabels("settings", DEFAULT_SETTINGS_LABELS, labels);
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
DEFAULT_SETTINGS_LABELS,
|
|
41
|
+
useSettingsLabels
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=settings-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/settings/settings-labels.ts"],"sourcesContent":["import { useKitLabels } from \"../i18n/kit-labels\";\n\n/**\n * The `settings` namespace (docs/settings-harmonization.md §4.3): the settings page's\n * own words, and the names and help lines of the five core groups every app shares —\n * appearance, account, security, notifications, data (§2.6). An app's own groups bring\n * their own words; any core label may be overridden like any kit label (prop >\n * `<UiKitProvider labels={{ settings }}>` > English).\n */\n\n/** A core group's two strings, as {@link SettingsGroup} takes them. */\nexport interface SettingsGroupLabels {\n /** The group's name. */\n title: string;\n /** One line: under the heading, and in the phone list. */\n help: string;\n}\n\n/** The five groups whose words the kit ships (§2.6, §4.2). */\nexport type SettingsCoreGroup = \"appearance\" | \"account\" | \"security\" | \"notifications\" | \"data\";\n\nexport interface SettingsLabels {\n /** The page's heading, and the back link's visible text on a phone's group page. */\n title: string;\n /** The search field's accessible name. */\n search: string;\n /** The search field's placeholder. */\n searchPlaceholder: string;\n /** The hit list's accessible name. */\n results: string;\n /** No hits for the trimmed query (§3.4). */\n noMatches: (query: string) => string;\n /** The button beside it that empties the field. */\n clearSearch: string;\n /** The phone's back link's accessible name: \"‹ Settings\" says where, this says what. */\n back: string;\n /** The sidebar's accessible name (the vertical tablist). */\n sections: string;\n /** A sidebar row's hit badge, read in place of the bare number while searching. */\n matchCount: (count: number) => string;\n /** The core groups' names and help lines. */\n groups: Record<SettingsCoreGroup, SettingsGroupLabels>;\n}\n\nexport const DEFAULT_SETTINGS_LABELS: SettingsLabels = {\n title: \"Settings\",\n search: \"Search settings\",\n searchPlaceholder: \"Search settings…\",\n results: \"Matching settings\",\n noMatches: (query) => `No settings match “${query}”.`,\n clearSearch: \"Clear search\",\n back: \"Back to settings\",\n sections: \"Settings sections\",\n matchCount: (count) => (count === 1 ? \"1 match\" : `${count} matches`),\n groups: {\n appearance: {\n title: \"Appearance\",\n help: \"Language, theme and how the app looks on this device.\",\n },\n account: {\n title: \"Account\",\n help: \"Your profile and your email address.\",\n },\n security: {\n title: \"Security\",\n help: \"How you sign in, and where you are signed in.\",\n },\n notifications: {\n title: \"Notifications\",\n help: \"What you are told about, when, and on which devices.\",\n },\n data: {\n title: \"Data\",\n help: \"Export what is yours, or delete it together with your account.\",\n },\n },\n};\n\n/**\n * 0.31.1: the `settings` namespace, resolved — English, then the provider, then `labels`.\n * What an app reads for its catalogue's core group titles and help lines, and for the\n * page title \"<group> · Settings · <app>\" (settings contract §3.1), without restating\n * the namespace and its defaults (Kurvenschmiede's 0.31 adoption).\n */\nexport function useSettingsLabels(labels?: Partial<SettingsLabels>): SettingsLabels {\n return useKitLabels(\"settings\", DEFAULT_SETTINGS_LABELS, labels);\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AA4CtB,MAAM,0BAA0C;AAAA,EACrD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,SAAS;AAAA,EACT,WAAW,CAAC,UAAU,2BAAsB,KAAK;AAAA,EACjD,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,YAAY,CAAC,UAAW,UAAU,IAAI,YAAY,GAAG,KAAK;AAAA,EAC1D,QAAQ;AAAA,IACN,YAAY;AAAA,MACV,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,UAAU;AAAA,MACR,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,eAAe;AAAA,MACb,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAQO,SAAS,kBAAkB,QAAkD;AAClF,SAAO,aAAa,YAAY,yBAAyB,MAAM;AACjE;","names":[]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ReactNode, ReactElement } from 'react';
|
|
2
|
+
import { SettingsGroup, SettingsEntry } from './settings-catalogue.js';
|
|
3
|
+
import { SettingsLabels } from './settings-labels.js';
|
|
4
|
+
import { SettingsLayoutMode } from './use-settings-route.js';
|
|
5
|
+
import '../search/search-index.js';
|
|
6
|
+
|
|
7
|
+
/** The page's width cap (§3.2): `6xl` for settings, `7xl` for keksdose's admin, whose
|
|
8
|
+
* user table scrolled sideways at 6xl (dev#488). */
|
|
9
|
+
type SettingsLayoutWidth = "6xl" | "7xl";
|
|
10
|
+
/** More entries than this and the search field shows by itself (§3.4): Kurvenschmiede has
|
|
11
|
+
* about twelve, keksdose 26; a page of six needs none. */
|
|
12
|
+
declare const SETTINGS_SEARCH_THRESHOLD = 8;
|
|
13
|
+
interface SettingsLayoutProps<G extends string> {
|
|
14
|
+
/** The page's `h1`: "Settings", "Administration". Default: `labels.title`. */
|
|
15
|
+
title?: ReactNode;
|
|
16
|
+
/** The groups, in order (§4.2). */
|
|
17
|
+
groups: readonly SettingsGroup<G>[];
|
|
18
|
+
/** One entry per setting (§4.1). Hidden entries leave search; a group left without a
|
|
19
|
+
* visible one is hidden. */
|
|
20
|
+
entries: readonly SettingsEntry<G>[];
|
|
21
|
+
/** `/settings` or `/admin`. The layout owns every path below it. */
|
|
22
|
+
basePath: string;
|
|
23
|
+
/** The group `/settings` opens on a desktop. Default: the first visible one. */
|
|
24
|
+
defaultGroup?: G;
|
|
25
|
+
/** Retired group ids and their successors, replaced in the address (§3.1). */
|
|
26
|
+
aliases?: Readonly<Partial<Record<string, G>>>;
|
|
27
|
+
/**
|
|
28
|
+
* The search field. Left out, it shows once more than {@link SETTINGS_SEARCH_THRESHOLD}
|
|
29
|
+
* entries are visible; `true` or `false` wins over the count — keksdose's admin keeps
|
|
30
|
+
* none (§3.4, §10.3). ⌘K gets the entries either way ({@link settingsSearchEntries}).
|
|
31
|
+
*/
|
|
32
|
+
search?: boolean;
|
|
33
|
+
/** See {@link SettingsLayoutWidth}. Default `"6xl"`. */
|
|
34
|
+
width?: SettingsLayoutWidth;
|
|
35
|
+
/** The group's cards, in one column. `sub` is the path segment after the group, which
|
|
36
|
+
* a card owns (`useSettingsLayout().selectSub`). */
|
|
37
|
+
renderGroup: (group: G, sub: string | null) => ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* Rows under the phone's group list that LEAVE settings — kastlan's "Administration",
|
|
40
|
+
* its import page (§3.3, §5). `ListItem`s with an `href`; the layout puts them in a list
|
|
41
|
+
* of their own, so they never read as a group. Not shown on a desktop, where the app's
|
|
42
|
+
* own navigation carries them.
|
|
43
|
+
*/
|
|
44
|
+
phoneFooter?: ReactNode;
|
|
45
|
+
/** Prop > `<UiKitProvider labels={{ settings }}>` > English. */
|
|
46
|
+
labels?: Partial<SettingsLabels>;
|
|
47
|
+
/** See {@link SettingsLayoutMode}. Default `"auto"`: the screen decides. */
|
|
48
|
+
layout?: SettingsLayoutMode;
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The settings page (docs/settings-harmonization.md §3), and the admin page built the
|
|
53
|
+
* same way (§5). keksdose's page is the reference for the desktop; the phone pattern and
|
|
54
|
+
* the path segments are new for all three apps.
|
|
55
|
+
*
|
|
56
|
+
* **Desktop** (§3.2): the page's `PageHeader`, the search field, then a 14rem sidebar —
|
|
57
|
+
* the kit's vertical `Tabs`, a link per group, sticky so a long group keeps the list in
|
|
58
|
+
* view — beside the open group: its heading (`h2`), its help line and its cards in ONE
|
|
59
|
+
* column, never a masonry (keksdose dev#490/#493).
|
|
60
|
+
*
|
|
61
|
+
* **Phone** (§3.3, below `md`): a drill-down. `/settings` is a list of the groups (icon,
|
|
62
|
+
* name, help line, chevron) with `phoneFooter` under it; a group is a page of its own with
|
|
63
|
+
* a back link, its name as the `h1` and the cards under it. It replaces keksdose's chip
|
|
64
|
+
* strip.
|
|
65
|
+
*
|
|
66
|
+
* **Routes, history and `?focus=`** are {@link useSettingsRoute}'s and
|
|
67
|
+
* {@link useSettingsFocus}'s. **Search** (§3.4) runs the kit's matcher over the catalogue:
|
|
68
|
+
* accents folded, ranked, over the title, the group's name and the keywords. On a desktop
|
|
69
|
+
* the sidebar dims the groups without hits and counts the others' while the hits replace
|
|
70
|
+
* the group; on a phone they replace the list. A hit is a link to its card.
|
|
71
|
+
*
|
|
72
|
+
* The cards inside read {@link useSettingsLayout} for `selectSub`, and their titles are
|
|
73
|
+
* headings under the group's ({@link SettingsHeadingLevel}).
|
|
74
|
+
*/
|
|
75
|
+
declare function SettingsLayout<G extends string>({ title, groups, entries, basePath, defaultGroup, aliases, search, width, renderGroup, phoneFooter, labels: labelsProp, layout, className, }: SettingsLayoutProps<G>): ReactElement;
|
|
76
|
+
|
|
77
|
+
export { SETTINGS_SEARCH_THRESHOLD, SettingsLayout, type SettingsLayoutProps, type SettingsLayoutWidth };
|