@caqophony/ui 0.3.1 → 0.4.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
@@ -48,11 +48,48 @@ Tailwind v4 colour/radius/z-index/opacity/duration utilities and registers the
48
48
  package's built files as a Tailwind `@source`. Override any `--token` after the
49
49
  import to re-theme.
50
50
 
51
- Tailwind v3 consumers: add `./node_modules/@caqophony/ui/dist/**/*.js` to
52
- `content` and map the same variables in `theme.extend.colors`.
51
+ ## Styles (Tailwind v3)
53
52
 
54
- `animate-in` / `animate-out` (used by tooltips) come from `tailwindcss-animate`
55
- (v3) or `tw-animate-css` (v4); without it those animations are simply absent.
53
+ Inside the Caqophony monorepo, an app takes the Gossamer preset and adds the
54
+ library to `content`. Do not map the tokens by hand in `theme.extend.colors`:
55
+ that left `bg-primary-solid` with no rule, or with a rule reading a variable
56
+ nothing declares, and neither one raises an error.
57
+
58
+ ```ts
59
+ import { gossamerPreset } from '@caqophony/tokens/tailwind-preset';
60
+
61
+ export default {
62
+ presets: [gossamerPreset],
63
+ darkMode: ['class'],
64
+ content: ['./app/**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'],
65
+ };
66
+ ```
67
+
68
+ The preset maps the library's tokens and declares its variables (light, and
69
+ dark under `.dark`), and adds the utilities and the `not-first:` variant a v3
70
+ theme cannot express. It only adds names: stock Tailwind keeps its values, your
71
+ `theme` wins over the preset, and a variable your CSS declares wins over the
72
+ preset's.
73
+
74
+ `pnpm exec tsx scripts/check-ui-classes-compile.ts` covers the two surfaces that
75
+ draw the library: the monorepo's product app on Tailwind v3, and a generated
76
+ app's `globals.css` plus `styles.css` on Tailwind v4, each against the classes
77
+ that surface can reach. It fails on a class Tailwind generates no rule for, or a
78
+ rule reading a variable that does not reach the element by default. It runs in
79
+ pre-push.
80
+
81
+ The preset is not published. Outside the monorepo, a Tailwind v3 app needs
82
+ `./node_modules/@caqophony/ui/dist/**/*.js` in `content` and the same mapping,
83
+ variables, utilities and variant in its own config, or Tailwind v4 and
84
+ `styles.css` above.
85
+
86
+ ## Animation and prose
87
+
88
+ On Tailwind v4, `styles.css` defines the `animate-in` / `animate-out` utilities
89
+ the modal and tooltip use, and loads `@tailwindcss/typography` for
90
+ `MarkdownRenderer`, which `@caqophony/ui` depends on. There is nothing to add.
91
+ On Tailwind v3, install `tailwindcss-animate` and `@tailwindcss/typography` as
92
+ plugins.
56
93
 
57
94
  ## Use
58
95
 
@@ -60,9 +97,25 @@ Tailwind v3 consumers: add `./node_modules/@caqophony/ui/dist/**/*.js` to
60
97
  import { Button, Card, PageContent } from '@caqophony/ui';
61
98
  import { BarChart } from '@caqophony/ui/charts';
62
99
  import { MarkdownRenderer } from '@caqophony/ui/markdown';
100
+ import { MarketingCta, MarketingFooter } from '@caqophony/ui/marketing';
101
+ import { Band, Display, Stack, Text } from '@caqophony/ui/material';
63
102
  ```
64
103
 
65
- Every entry point carries a `'use client'` directive.
104
+ `@caqophony/ui/material` holds what a designed page is composed from: bands,
105
+ display type, layout, and flat illustration pieces. Each colour is a token, so a
106
+ theme change reaches all of it. Set a `Band`'s `surface` to `night` and every
107
+ library component inside reads on the dark ground, with no prop of its own.
108
+
109
+ `@caqophony/ui/material-tones` holds `FILL` and `STROKE`, the classes art draws
110
+ in. It and `@caqophony/ui/material-art` are the entries with no `'use client'`
111
+ directive, so a server component can use them. Every other entry point carries
112
+ the directive.
113
+
114
+ An app's own art is data, not code: a closed tree of SVG elements in tones, in a
115
+ `*.art.json` file in `art/` beside `app/`, outside the library. `Art` from
116
+ `@caqophony/ui/material-art` draws it, as a `Figure`'s one child, and nothing else
117
+ makes elements in the slot. Shade `paper` with a held step at low opacity: `paper`
118
+ keeps its colour in dark mode and `mist-*` does not.
66
119
 
67
120
  ## Licence
68
121
 
@@ -0,0 +1,229 @@
1
+ 'use client';
2
+ import {
3
+ cn
4
+ } from "./chunk-DP5WQAPO.js";
5
+
6
+ // src/icon.tsx
7
+ import {
8
+ ArrowLeft,
9
+ ArrowRight,
10
+ Bell,
11
+ Calendar,
12
+ Check,
13
+ ChevronDown,
14
+ ChevronLeft,
15
+ ChevronRight,
16
+ ChevronUp,
17
+ CircleAlert,
18
+ CircleCheck,
19
+ ClipboardList,
20
+ Clock,
21
+ Database,
22
+ Download,
23
+ Ellipsis,
24
+ ExternalLink,
25
+ Eye,
26
+ EyeOff,
27
+ FileText,
28
+ Filter,
29
+ Folder,
30
+ Heart,
31
+ House,
32
+ Inbox,
33
+ Info,
34
+ LayoutGrid,
35
+ Link,
36
+ List,
37
+ LoaderCircle,
38
+ Lock,
39
+ LogOut,
40
+ Mail,
41
+ MapPin,
42
+ Menu,
43
+ Package,
44
+ Pencil,
45
+ Phone,
46
+ Plus,
47
+ RefreshCw,
48
+ Rocket,
49
+ Search,
50
+ Settings,
51
+ Star,
52
+ Tag,
53
+ Trash2,
54
+ TriangleAlert,
55
+ Upload,
56
+ User,
57
+ Users,
58
+ X
59
+ } from "lucide-react";
60
+ import { jsx } from "react/jsx-runtime";
61
+ var ICONS = {
62
+ "arrow-left": ArrowLeft,
63
+ "arrow-right": ArrowRight,
64
+ bell: Bell,
65
+ calendar: Calendar,
66
+ check: Check,
67
+ "chevron-down": ChevronDown,
68
+ "chevron-left": ChevronLeft,
69
+ "chevron-right": ChevronRight,
70
+ "chevron-up": ChevronUp,
71
+ "circle-alert": CircleAlert,
72
+ "circle-check": CircleCheck,
73
+ "clipboard-list": ClipboardList,
74
+ clock: Clock,
75
+ database: Database,
76
+ download: Download,
77
+ ellipsis: Ellipsis,
78
+ "external-link": ExternalLink,
79
+ eye: Eye,
80
+ "eye-off": EyeOff,
81
+ "file-text": FileText,
82
+ filter: Filter,
83
+ folder: Folder,
84
+ heart: Heart,
85
+ house: House,
86
+ inbox: Inbox,
87
+ info: Info,
88
+ "layout-grid": LayoutGrid,
89
+ link: Link,
90
+ list: List,
91
+ loader: LoaderCircle,
92
+ lock: Lock,
93
+ "log-out": LogOut,
94
+ mail: Mail,
95
+ "map-pin": MapPin,
96
+ menu: Menu,
97
+ package: Package,
98
+ pencil: Pencil,
99
+ phone: Phone,
100
+ plus: Plus,
101
+ "refresh-cw": RefreshCw,
102
+ rocket: Rocket,
103
+ search: Search,
104
+ settings: Settings,
105
+ star: Star,
106
+ tag: Tag,
107
+ trash: Trash2,
108
+ "triangle-alert": TriangleAlert,
109
+ upload: Upload,
110
+ user: User,
111
+ users: Users,
112
+ x: X
113
+ };
114
+ var ICON_SIZES = {
115
+ xs: "size-3",
116
+ sm: "size-4",
117
+ md: "size-5",
118
+ lg: "size-6",
119
+ xl: "size-8"
120
+ };
121
+ function Icon({ name, size, className }) {
122
+ const Glyph = ICONS[name];
123
+ return /* @__PURE__ */ jsx(Glyph, { className: cn(ICON_SIZES[size], className), "aria-hidden": "true" });
124
+ }
125
+
126
+ // src/scroll-hint.ts
127
+ var SCROLL_HINT_X = "";
128
+ var SCROLL_HINT_X_MARK = "pointer-events-none sticky right-0 z-10 ml-2 flex w-6 shrink-0 items-center justify-end self-stretch data-[scroll-hint-state=end]:invisible data-[scroll-hint-state=none]:hidden";
129
+ var SCROLL_HINT_X_PILL = "flex h-full max-h-6 w-6 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-sm";
130
+ var SCROLL_HINT_Y = "[container-type:scroll-state]";
131
+ var SCROLL_HINT_Y_MARK = "pointer-events-none sticky bottom-1 -mt-7 flex h-6 justify-center supports-[container-type:scroll-state]:[@container_not_scroll-state(scrollable:bottom)]:hidden";
132
+
133
+ // src/scroll-hint-mark.tsx
134
+ import * as React from "react";
135
+
136
+ // src/scroll-hint-watch.ts
137
+ function scrollHintXState(box, slotWidth) {
138
+ if (box.scrollWidth - slotWidth <= box.clientWidth + 1) return "none";
139
+ return Math.abs(box.scrollLeft) + box.clientWidth >= box.scrollWidth - 1 ? "end" : "more";
140
+ }
141
+ function scrollHintXSlotWidth(mark, scroller) {
142
+ const own = getComputedStyle(mark);
143
+ if (own.display === "none") return 0;
144
+ const px = (v, what) => {
145
+ if (v === "normal") return 0;
146
+ const n = Number.parseFloat(v);
147
+ if (!Number.isFinite(n)) {
148
+ throw new Error(
149
+ `scrollHintXSlotWidth: ${what} is "${v}", not a length in px`
150
+ );
151
+ }
152
+ return n;
153
+ };
154
+ const gap = mark.previousElementSibling === null ? 0 : px(getComputedStyle(scroller).columnGap, "the scroller's column-gap");
155
+ return mark.getBoundingClientRect().width + px(own.marginLeft, "the mark's margin-left") + px(own.marginRight, "the mark's margin-right") + gap;
156
+ }
157
+ function watchScrollHintX(mark, write) {
158
+ const scroller = mark.parentElement;
159
+ if (scroller === null) {
160
+ throw new Error(
161
+ "watchScrollHintX: the mark has no parent element; it must be the last child of the box that scrolls on x"
162
+ );
163
+ }
164
+ let last;
165
+ const update = () => {
166
+ const state = scrollHintXState(
167
+ scroller,
168
+ scrollHintXSlotWidth(mark, scroller)
169
+ );
170
+ if (state === last) return;
171
+ last = state;
172
+ write(state);
173
+ };
174
+ const resize = new ResizeObserver(update);
175
+ const observeChildren = () => {
176
+ for (const child of Array.from(scroller.children)) resize.observe(child);
177
+ };
178
+ resize.observe(scroller);
179
+ observeChildren();
180
+ const added = new MutationObserver(() => {
181
+ observeChildren();
182
+ update();
183
+ });
184
+ added.observe(scroller, { childList: true });
185
+ scroller.addEventListener("scroll", update, { passive: true });
186
+ update();
187
+ return () => {
188
+ scroller.removeEventListener("scroll", update);
189
+ resize.disconnect();
190
+ added.disconnect();
191
+ };
192
+ }
193
+
194
+ // src/scroll-hint-mark.tsx
195
+ import { jsx as jsx2 } from "react/jsx-runtime";
196
+ function ScrollHintXMark() {
197
+ const ref = React.useRef(null);
198
+ React.useEffect(() => {
199
+ const mark = ref.current;
200
+ if (mark === null) {
201
+ throw new Error(
202
+ "ScrollHintXMark: the effect ran with no mounted mark (ref.current is null)"
203
+ );
204
+ }
205
+ return watchScrollHintX(mark, (state) => {
206
+ mark.dataset.scrollHintState = state;
207
+ });
208
+ }, []);
209
+ return /* @__PURE__ */ jsx2(
210
+ "div",
211
+ {
212
+ ref,
213
+ "aria-hidden": "true",
214
+ "data-scroll-hint": "x",
215
+ className: SCROLL_HINT_X_MARK,
216
+ children: /* @__PURE__ */ jsx2("span", { className: SCROLL_HINT_X_PILL, children: /* @__PURE__ */ jsx2(Icon, { name: "chevron-right", size: "sm" }) })
217
+ }
218
+ );
219
+ }
220
+
221
+ export {
222
+ ICONS,
223
+ ICON_SIZES,
224
+ Icon,
225
+ SCROLL_HINT_X,
226
+ SCROLL_HINT_Y,
227
+ SCROLL_HINT_Y_MARK,
228
+ ScrollHintXMark
229
+ };
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ // src/utils.ts
4
+ import { clsx } from "clsx";
5
+ import { extendTailwindMerge } from "tailwind-merge";
6
+ var merge = extendTailwindMerge({
7
+ extend: {
8
+ classGroups: {
9
+ "font-size": [
10
+ {
11
+ text: ["display-poster", "display-headline", "display-title", "lede"]
12
+ }
13
+ ]
14
+ }
15
+ }
16
+ });
17
+ function cn(...inputs) {
18
+ return merge(clsx(inputs));
19
+ }
20
+
21
+ export {
22
+ cn
23
+ };
@@ -1,18 +1,12 @@
1
1
  'use client';
2
+ import {
3
+ cn
4
+ } from "./chunk-DP5WQAPO.js";
2
5
 
3
6
  // src/markdown-renderer.tsx
4
7
  import { memo } from "react";
5
8
  import ReactMarkdown from "react-markdown";
6
9
  import remarkGfm from "remark-gfm";
7
-
8
- // src/utils.ts
9
- import { clsx } from "clsx";
10
- import { twMerge } from "tailwind-merge";
11
- function cn(...inputs) {
12
- return twMerge(clsx(inputs));
13
- }
14
-
15
- // src/markdown-renderer.tsx
16
10
  import { Fragment, jsx } from "react/jsx-runtime";
17
11
  var components = {
18
12
  // Links open in new tab
@@ -67,6 +61,5 @@ var MarkdownRenderer = memo(
67
61
  MarkdownRenderer.displayName = "MarkdownRenderer";
68
62
 
69
63
  export {
70
- cn,
71
64
  MarkdownRenderer
72
65
  };