@super-calendar/dom 2.1.5 → 2.2.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/src/slots.ts ADDED
@@ -0,0 +1,87 @@
1
+ // Per-slot styling for the DOM renderer.
2
+ //
3
+ // Every component styles itself with inline styles driven by the theme object, so
4
+ // zero-config consumers get a full look with no stylesheet. But inline styles beat
5
+ // external CSS, so a Tailwind (or plain CSS) class can't override them. This module
6
+ // closes that gap: each styleable element is a named "slot" that accepts a `class`
7
+ // and/or an inline `style` override.
8
+ //
9
+ // The contract: a slot's *structural* styles (the layout the component needs to not
10
+ // fall apart -- grid templates, flex, positioning) always apply. Its *themed* styles
11
+ // (colours, typography, spacing, borders) apply only when no class is supplied for
12
+ // that slot; supply a class and you own those, so your Tailwind classes win instead
13
+ // of losing to inline styles. A per-slot inline `style` override always merges last.
14
+
15
+ import type { CSSProperties } from "react";
16
+
17
+ /** Join truthy class names (a dependency-free clsx-lite). */
18
+ export function cn(...parts: Array<string | false | null | undefined>): string | undefined {
19
+ const out = parts.filter(Boolean).join(" ");
20
+ return out || undefined;
21
+ }
22
+
23
+ /**
24
+ * Per-slot styling overrides. Give a slot a Tailwind/CSS class and/or an inline
25
+ * style; missing slots keep the built-in look.
26
+ */
27
+ export interface SlotStyleProps<Slot extends string> {
28
+ /**
29
+ * Class names per slot (e.g. Tailwind). Supplying a class for a slot drops the
30
+ * built-in *themed* inline styles for that slot so the class fully controls its
31
+ * look; the *structural* styles the layout depends on are kept.
32
+ */
33
+ classNames?: Partial<Record<Slot, string>>;
34
+ /** Inline style overrides per slot, merged last (win over defaults and classes). */
35
+ styles?: Partial<Record<Slot, CSSProperties>>;
36
+ }
37
+
38
+ /** A slot's built-in styling, split so classes can replace the look but not the layout. */
39
+ export interface SlotDefault {
40
+ /** Structural styles kept even when a class is supplied. */
41
+ base?: CSSProperties;
42
+ /** Themed styles (colour, type, spacing) dropped when a class is supplied. */
43
+ themed?: CSSProperties;
44
+ }
45
+
46
+ /** The props a resolved slot spreads onto an element. */
47
+ export interface ResolvedSlot {
48
+ className: string | undefined;
49
+ style: CSSProperties | undefined;
50
+ "data-slot": string;
51
+ }
52
+
53
+ /**
54
+ * Build a slot resolver for one render. `slot(name, defaults)` returns the props to
55
+ * spread on that element: a merged `className`, the resolved inline `style`, and a
56
+ * stable `data-slot` hook for CSS/testing.
57
+ */
58
+ export function createSlots<Slot extends string>({ classNames, styles }: SlotStyleProps<Slot>) {
59
+ return (name: Slot, defaults?: SlotDefault): ResolvedSlot => {
60
+ const slotClass = classNames?.[name];
61
+ const override = styles?.[name];
62
+ const style: CSSProperties = {
63
+ ...defaults?.base,
64
+ ...(slotClass ? undefined : defaults?.themed),
65
+ ...override,
66
+ };
67
+ return {
68
+ className: cn(slotClass),
69
+ // Omit an empty style object so the DOM stays clean when a slot has no styles.
70
+ style: Object.keys(style).length ? style : undefined,
71
+ "data-slot": name,
72
+ };
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Turn a map of boolean state flags into present/absent `data-*` attributes, so
78
+ * consumers can target state with CSS/Tailwind variants (e.g. `data-[today]:...`).
79
+ * A true flag renders the attribute (empty value); a false/undefined one omits it.
80
+ */
81
+ export function dataState(
82
+ flags: Record<string, boolean | undefined>,
83
+ ): Record<string, "" | undefined> {
84
+ const out: Record<string, "" | undefined> = {};
85
+ for (const key in flags) if (flags[key]) out[key] = "";
86
+ return out;
87
+ }