rei-kit 0.7.0 → 0.8.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 CHANGED
@@ -16,17 +16,41 @@ That is the whole trick, and everything else follows from it.
16
16
 
17
17
  ### What gets to be in here
18
18
 
19
- Three things have to be true at once:
20
-
21
- 1. **Two apps genuinely need it** measured, not predicted. A component one
22
- app needs stays in that app however general it looks.
23
- 2. **It adds no required peer.** Anything that needs a new library goes behind
24
- its own entry point, or stays in the app.
25
- 3. **It contains no product decision** no colour, no copy, no icon. Those
26
- arrive as props and slots.
27
-
28
- The corollary is the useful one: **a file that is 90% identical in two apps is
29
- a kit candidate.** The measure is `diff`, not taste.
19
+ The kit is not built from what its current apps happen to need. It is built to
20
+ be the thing the next app starts from. So the rule depends on what kind of
21
+ thing is being added and conflating the two cost this package three releases
22
+ that shipped parts nobody could use.
23
+
24
+ **Primitives are complete by construction.** A button, a field, a toast, a
25
+ modal. There is no uncertainty about whether the next app will want one, so
26
+ they do not wait for a second consumer: waiting means every new app begins by
27
+ copying, which is the thing this package exists to prevent. A primitive is
28
+ finished when two checkable things are true:
29
+
30
+ - **It covers every role the design system declares.** `tokens.css` names five
31
+ colour roles while `BaseButton` exposed two, so an app that wanted a
32
+ success-coloured action hand-wrote the button. A component that cannot use a
33
+ role its own token file declares is not being careful; it is incomplete.
34
+ - **The app can take the behaviour without the appearance.** `variant="unstyled"`
35
+ exists because 58 raw `<button>` elements sat in 24 files that already
36
+ imported `BaseButton`. A picker cell, a chip, a calendar day: the surface is
37
+ the app's and should be, but the element, the focus ring, the disabled
38
+ handling and the `aria-pressed` bookkeeping are not — and they were being
39
+ rewritten every time, usually without the focus ring.
40
+
41
+ **Composed components wait for two apps.** A `PriceCard`, a `TourShell`, a
42
+ `DangerZone`. These carry a shape, and a shape designed from one example is
43
+ designed wrong. Here the measure is `diff`, not taste: **a file that is 90%
44
+ identical in two apps is a kit candidate.**
45
+
46
+ **Both kinds:** no new required peer — anything needing a library goes behind
47
+ its own entry point or stays in the app — and no product decision. No colour,
48
+ no copy, no icon. Those arrive as props and slots.
49
+
50
+ **And one test worth more than the rule:** a raw `<button>` in a file that
51
+ already imports `BaseButton` is a bug in the kit, not in the app. That grep
52
+ found every gap closed between 0.5.0 and 0.7.0, and it found them after those
53
+ releases had claimed to be finished.
30
54
 
31
55
  ## Status
32
56
 
@@ -13,9 +13,9 @@ type __VLS_ModelProps = {
13
13
  modelValue: boolean;
14
14
  };
15
15
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
16
- declare var __VLS_18: {};
16
+ declare var __VLS_26: {};
17
17
  type __VLS_Slots = {} & {
18
- default?: (props: typeof __VLS_18) => any;
18
+ default?: (props: typeof __VLS_26) => any;
19
19
  };
20
20
  declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
21
21
  "update:modelValue": (value: boolean) => any;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Where the toasts land. One of these, at the app root.
3
+ *
4
+ * @example
5
+ * ```vue
6
+ * <!-- App.vue -->
7
+ * <ToastHost :close-label="$t('common.close')" />
8
+ * ```
9
+ */
10
+ type __VLS_Props = {
11
+ /**
12
+ * The accessible name of each dismiss button. Required, because the button
13
+ * is an X and an X has no name — and the kit does not know the language.
14
+ */
15
+ closeLabel: string;
16
+ /**
17
+ * Stack from the bottom instead of the top. For a phone shell, where the top
18
+ * is a status bar and a header and the thumb is nowhere near it.
19
+ */
20
+ bottom?: boolean | undefined;
21
+ };
22
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Saying that something happened.
3
+ *
4
+ * The reason this exists is a measurement rather than a preference: the word
5
+ * "toast" appeared **zero times** across all three consuming apps. Not because
6
+ * they had decided against it — because there was no mechanism, so every save,
7
+ * every delete and every export finished in silence and the only way to know
8
+ * it had worked was that nothing had visibly broken.
9
+ *
10
+ * ── What is deliberately not here ──
11
+ *
12
+ * **No text.** The kit never knows a sentence. Callers pass the message; a
13
+ * component that called a translator would force one on the app.
14
+ *
15
+ * **Not for form errors.** A field that was rejected says so beside itself,
16
+ * where the reader's eye already is and where it stays until fixed. A toast
17
+ * that disappears after four seconds is the wrong place for something the
18
+ * reader has to act on. Use `BaseAlert` and `FormField` for those; use this
19
+ * for what has already happened.
20
+ *
21
+ * **A singleton, on purpose.** Two hosts would mean two stacks racing for the
22
+ * same corner. The store lives at module scope and `ToastHost` renders it.
23
+ */
24
+ export type ToastTone = 'info' | 'success' | 'warning' | 'danger';
25
+ export interface Toast {
26
+ readonly id: number;
27
+ readonly message: string;
28
+ readonly tone: ToastTone;
29
+ /** Milliseconds on screen. `0` stays until dismissed. */
30
+ readonly duration: number;
31
+ }
32
+ export interface ToastOptions {
33
+ /** Milliseconds on screen; `0` stays until dismissed. */
34
+ duration?: number | undefined;
35
+ }
36
+ /** Removes a toast, whether it timed out or was dismissed. */
37
+ declare function dismiss(id: number): void;
38
+ /** Removes everything on screen. For a route change, or a sign-out. */
39
+ declare function dismissAll(): void;
40
+ /**
41
+ * Stops the clock on a toast the reader is pointing at.
42
+ *
43
+ * Somebody who has moved the pointer onto it is reading it, and taking it away
44
+ * mid-sentence is the one thing a notification must not do.
45
+ */
46
+ declare function pause(id: number): void;
47
+ /** Starts it again, from where it stopped rather than from the beginning. */
48
+ declare function resume(id: number): void;
49
+ /**
50
+ * The stack, and the four ways to add to it.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const toast = useToast()
55
+ *
56
+ * toast.success(t('habit.saved'))
57
+ * toast.danger(t('common.failed'), { duration: 0 }) // stays until dismissed
58
+ *
59
+ * const id = toast.info(t('export.preparing'), { duration: 0 })
60
+ * toast.dismiss(id)
61
+ * ```
62
+ */
63
+ export declare function useToast(): {
64
+ /** Every toast on screen, oldest first. `ToastHost` renders this. */
65
+ toasts: Readonly<import('vue').Ref<readonly {
66
+ readonly id: number;
67
+ readonly message: string;
68
+ readonly tone: ToastTone;
69
+ readonly duration: number;
70
+ }[], readonly {
71
+ readonly id: number;
72
+ readonly message: string;
73
+ readonly tone: ToastTone;
74
+ readonly duration: number;
75
+ }[]>>;
76
+ info: (message: string, options?: ToastOptions) => number;
77
+ success: (message: string, options?: ToastOptions) => number;
78
+ warning: (message: string, options?: ToastOptions) => number;
79
+ danger: (message: string, options?: ToastOptions) => number;
80
+ dismiss: typeof dismiss;
81
+ dismissAll: typeof dismissAll;
82
+ pause: typeof pause;
83
+ resume: typeof resume;
84
+ };
85
+ export {};
package/dist/index.d.ts CHANGED
@@ -40,6 +40,8 @@ export { useDebouncedCallback } from './composables/use-debounced-callback';
40
40
  export { useDragScroll } from './composables/use-drag-scroll';
41
41
  export { useMediaQuery } from './composables/use-media-query';
42
42
  export { useVisualViewport } from './composables/use-visual-viewport';
43
+ export { useToast } from './composables/use-toast';
44
+ export type { Toast, ToastOptions, ToastTone } from './composables/use-toast';
43
45
  export type { VisualViewportRect } from './composables/use-visual-viewport';
44
46
  export { default as BaseAlert } from './components/BaseAlert.vue';
45
47
  export { default as BaseBadge } from './components/BaseBadge.vue';
@@ -64,6 +66,7 @@ export { default as SettingsGroup } from './components/SettingsGroup.vue';
64
66
  export { default as SettingsRow } from './components/SettingsRow.vue';
65
67
  export { default as SkeletonList } from './components/SkeletonList.vue';
66
68
  export { default as StatCard } from './components/StatCard.vue';
69
+ export { default as ToastHost } from './components/ToastHost.vue';
67
70
  export { default as ToneDot } from './components/ToneDot.vue';
68
71
  export type { Tone } from './components/SectionHeading.vue';
69
72
  export { default as LocaleLinks } from './components/LocaleLinks.vue';