@eduardoalvarez/arrecife 0.6.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +288 -49
  3. package/dist/brand/index.cjs +2 -2
  4. package/dist/brand/index.js +3 -3
  5. package/dist/chart/index.cjs +129 -2
  6. package/dist/chart/index.d.cts +114 -4
  7. package/dist/chart/index.d.ts +114 -4
  8. package/dist/chart/index.js +129 -5
  9. package/dist/{chunk-PMN7NR3G.js → chunk-5A5GH2PF.js} +1 -1
  10. package/dist/{chunk-DKCN7BAL.js → chunk-6IGD5REB.js} +1 -1
  11. package/dist/{chunk-XKYHTOUJ.js → chunk-FAAGZG7A.js} +1 -1
  12. package/dist/{chunk-O4TAH7YJ.js → chunk-FGFNK72B.js} +29 -5
  13. package/dist/chunk-HOADZ6GS.js +72 -0
  14. package/dist/chunk-LXRGQKMG.js +145 -0
  15. package/dist/{chunk-6O3KWB6P.js → chunk-MPZBF2TZ.js} +2 -2
  16. package/dist/{chunk-JMOOFZ3B.js → chunk-TRPBID2W.js} +1 -1
  17. package/dist/{chunk-25YNFCIF.js → chunk-XXDATT3A.js} +6 -3
  18. package/dist/doctor.mjs +248 -0
  19. package/dist/form/index.cjs +2 -2
  20. package/dist/form/index.d.cts +1 -1
  21. package/dist/form/index.d.ts +1 -1
  22. package/dist/form/index.js +4 -4
  23. package/dist/icons/index.cjs +149 -0
  24. package/dist/icons/index.d.cts +94 -0
  25. package/dist/icons/index.d.ts +94 -0
  26. package/dist/icons/index.js +28 -0
  27. package/dist/index-BbRplw_B.d.cts +58 -0
  28. package/dist/index-BbRplw_B.d.ts +58 -0
  29. package/dist/index.cjs +425 -251
  30. package/dist/index.d.cts +303 -104
  31. package/dist/index.d.ts +303 -104
  32. package/dist/index.js +283 -285
  33. package/dist/{label-MgHFKnFy.d.ts → label-DJ4HuD-R.d.cts} +3 -2
  34. package/dist/{label-MgHFKnFy.d.cts → label-DJ4HuD-R.d.ts} +3 -2
  35. package/dist/og/index.cjs +3 -2
  36. package/dist/og/index.js +1 -1
  37. package/dist/shiki/index.js +1 -1
  38. package/dist/social/data.cjs +161 -0
  39. package/dist/social/data.d.cts +161 -0
  40. package/dist/social/data.d.ts +161 -0
  41. package/dist/social/data.js +2 -0
  42. package/dist/social/index.cjs +153 -0
  43. package/dist/social/index.d.cts +2 -0
  44. package/dist/social/index.d.ts +2 -0
  45. package/dist/social/index.js +3 -0
  46. package/dist/tokens/index.cjs +29 -5
  47. package/dist/tokens/index.d.cts +37 -10
  48. package/dist/tokens/index.d.ts +37 -10
  49. package/dist/tokens/index.js +2 -2
  50. package/dist/tokens/theme.css +79 -22
  51. package/dist/variants/index.cjs +6 -3
  52. package/dist/variants/index.d.cts +6 -3
  53. package/dist/variants/index.d.ts +6 -3
  54. package/dist/variants/index.js +1 -1
  55. package/llms.txt +530 -80
  56. package/package.json +29 -2
@@ -0,0 +1,72 @@
1
+ import { gitHubGlyph, linkedInGlyph, xGlyph, instagramGlyph, discordGlyph, youTubeGlyph, rssGlyph, emailGlyph, newsletterGlyph, websiteGlyph, SOCIAL_STROKE_WIDTH, SOCIAL_VIEW_BOX } from './chunk-LXRGQKMG.js';
2
+ import { __export } from './chunk-MLKGABMK.js';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/social/index.tsx
6
+ var social_exports = {};
7
+ __export(social_exports, {
8
+ Discord: () => Discord,
9
+ Email: () => Email,
10
+ GitHub: () => GitHub,
11
+ Instagram: () => Instagram,
12
+ LinkedIn: () => LinkedIn,
13
+ Newsletter: () => Newsletter,
14
+ Rss: () => Rss,
15
+ Website: () => Website,
16
+ X: () => X,
17
+ YouTube: () => YouTube
18
+ });
19
+ var SOLID = { fill: "currentColor", stroke: "none" };
20
+ function drawShape(shape, index) {
21
+ if (shape.tag === "path") {
22
+ return /* @__PURE__ */ jsx("path", { d: shape.d, ...shape.solid ? SOLID : {} }, index);
23
+ }
24
+ if (shape.tag === "circle") {
25
+ return /* @__PURE__ */ jsx("circle", { cx: shape.cx, cy: shape.cy, r: shape.r, ...shape.solid ? SOLID : {} }, index);
26
+ }
27
+ return /* @__PURE__ */ jsx(
28
+ "rect",
29
+ {
30
+ x: shape.x,
31
+ y: shape.y,
32
+ width: shape.width,
33
+ height: shape.height,
34
+ rx: shape.rx
35
+ },
36
+ index
37
+ );
38
+ }
39
+ function Glyph({ glyph, ...props }) {
40
+ const brand = glyph.kind === "brand";
41
+ return /* @__PURE__ */ jsx(
42
+ "svg",
43
+ {
44
+ viewBox: SOCIAL_VIEW_BOX,
45
+ width: "1em",
46
+ height: "1em",
47
+ fill: brand ? "currentColor" : "none",
48
+ ...brand ? {} : {
49
+ stroke: "currentColor",
50
+ strokeWidth: SOCIAL_STROKE_WIDTH,
51
+ strokeLinecap: "round",
52
+ strokeLinejoin: "round"
53
+ },
54
+ "aria-hidden": "true",
55
+ focusable: "false",
56
+ ...props,
57
+ children: glyph.shapes.map(drawShape)
58
+ }
59
+ );
60
+ }
61
+ var GitHub = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: gitHubGlyph, ...props });
62
+ var LinkedIn = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: linkedInGlyph, ...props });
63
+ var X = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: xGlyph, ...props });
64
+ var Instagram = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: instagramGlyph, ...props });
65
+ var Discord = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: discordGlyph, ...props });
66
+ var YouTube = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: youTubeGlyph, ...props });
67
+ var Rss = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: rssGlyph, ...props });
68
+ var Email = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: emailGlyph, ...props });
69
+ var Newsletter = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: newsletterGlyph, ...props });
70
+ var Website = (props) => /* @__PURE__ */ jsx(Glyph, { glyph: websiteGlyph, ...props });
71
+
72
+ export { Discord, Email, GitHub, Instagram, LinkedIn, Newsletter, Rss, Website, X, YouTube, social_exports };
@@ -0,0 +1,145 @@
1
+ // src/social/data.ts
2
+ var SOCIAL_STROKE_WIDTH = 1.6;
3
+ var SOCIAL_VIEW_BOX = "0 0 24 24";
4
+ var gitHubGlyph = {
5
+ kind: "brand",
6
+ shapes: [
7
+ {
8
+ tag: "path",
9
+ d: "M12 .5C5.73.5.5 5.73.5 12a11.5 11.5 0 0 0 7.86 10.91c.58.1.79-.25.79-.55 0-.27-.01-1.18-.02-2.14-3.2.7-3.88-1.36-3.88-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.79 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.12 3.05.74.8 1.18 1.83 1.18 3.09 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.06.78 2.14 0 1.55-.01 2.79-.01 3.17 0 .3.2.66.79.55A11.5 11.5 0 0 0 23.5 12C23.5 5.73 18.27.5 12 .5Z"
10
+ }
11
+ ]
12
+ };
13
+ var linkedInGlyph = {
14
+ kind: "brand",
15
+ shapes: [
16
+ {
17
+ tag: "path",
18
+ d: "M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM3 9h4v12H3V9Zm7 0h3.8v1.71h.05a4.17 4.17 0 0 1 3.75-2.06c4.01 0 4.75 2.64 4.75 6.07V21h-4v-5.4c0-1.29-.02-2.95-1.8-2.95-1.8 0-2.08 1.4-2.08 2.85V21h-3.97V9Z"
19
+ }
20
+ ]
21
+ };
22
+ var xGlyph = {
23
+ kind: "brand",
24
+ shapes: [
25
+ {
26
+ tag: "path",
27
+ d: "M17.53 3h3.06l-6.69 7.64L21.75 21h-6.16l-4.83-6.3L5.25 21H2.19l7.15-8.17L2.5 3h6.32l4.36 5.77L17.53 3Zm-1.07 16.17h1.7L7.62 4.74H5.8l10.66 14.43Z"
28
+ }
29
+ ]
30
+ };
31
+ var instagramGlyph = {
32
+ kind: "brand",
33
+ shapes: [
34
+ {
35
+ tag: "path",
36
+ d: "M12 2.16c3.2 0 3.58.01 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s-.01 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58-.01-4.85-.07c-1.17-.05-1.8-.25-2.23-.41-.56-.22-.96-.48-1.38-.9-.42-.42-.68-.82-.9-1.38-.16-.42-.36-1.06-.41-2.23C2.17 15.58 2.16 15.2 2.16 12s.01-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.17 8.8 2.16 12 2.16Zm0 3.68a6.16 6.16 0 1 0 0 12.32 6.16 6.16 0 0 0 0-12.32Zm0 10.16a4 4 0 1 1 0-8 4 4 0 0 1 0 8Zm7.85-10.4a1.44 1.44 0 1 1-2.88 0 1.44 1.44 0 0 1 2.88 0Z"
37
+ }
38
+ ]
39
+ };
40
+ var discordGlyph = {
41
+ kind: "brand",
42
+ shapes: [
43
+ {
44
+ tag: "path",
45
+ d: "M20.32 5.56A18.5 18.5 0 0 0 15.7 4.1a.07.07 0 0 0-.07.04c-.2.36-.42.82-.58 1.19a17 17 0 0 0-5.1 0c-.16-.38-.39-.83-.59-1.19a.07.07 0 0 0-.07-.04c-1.6.28-3.15.77-4.62 1.46a.06.06 0 0 0-.03.03C1.66 10 .9 14.28 1.28 18.5a.08.08 0 0 0 .03.05 18.6 18.6 0 0 0 5.6 2.84.07.07 0 0 0 .08-.03c.43-.59.81-1.21 1.14-1.86a.07.07 0 0 0-.04-.1c-.6-.23-1.18-.51-1.74-.83a.07.07 0 0 1-.01-.12l.35-.27a.07.07 0 0 1 .07-.01 13.3 13.3 0 0 0 11.3 0 .07.07 0 0 1 .08.01l.34.27c.04.04.04.1-.01.12-.55.33-1.13.6-1.74.83a.07.07 0 0 0-.04.1c.34.65.72 1.27 1.14 1.86a.07.07 0 0 0 .08.03 18.5 18.5 0 0 0 5.6-2.84.07.07 0 0 0 .04-.05c.45-4.88-.75-9.12-3.18-12.88a.06.06 0 0 0-.03-.03ZM8.4 15.93c-1.1 0-2.01-1.01-2.01-2.25 0-1.24.89-2.25 2.01-2.25 1.13 0 2.03 1.02 2.02 2.25 0 1.24-.9 2.25-2.02 2.25Zm7.22 0c-1.1 0-2.01-1.01-2.01-2.25 0-1.24.89-2.25 2.01-2.25 1.13 0 2.03 1.02 2.02 2.25 0 1.24-.89 2.25-2.02 2.25Z"
46
+ }
47
+ ]
48
+ };
49
+ var youTubeGlyph = {
50
+ kind: "brand",
51
+ shapes: [
52
+ {
53
+ tag: "path",
54
+ d: "M23.5 6.9a3 3 0 0 0-2.12-2.12C19.5 4.27 12 4.27 12 4.27s-7.5 0-9.38.51A3 3 0 0 0 .5 6.9 31.3 31.3 0 0 0 0 12a31.3 31.3 0 0 0 .5 5.1 3 3 0 0 0 2.12 2.12c1.88.51 9.38.51 9.38.51s7.5 0 9.38-.51a3 3 0 0 0 2.12-2.12A31.3 31.3 0 0 0 24 12a31.3 31.3 0 0 0-.5-5.1ZM9.6 15.6V8.4l6.24 3.6-6.24 3.6Z"
55
+ }
56
+ ]
57
+ };
58
+ var rssGlyph = {
59
+ kind: "functional",
60
+ shapes: [
61
+ { tag: "path", d: "M4 11a9 9 0 0 1 9 9M4 4a16 16 0 0 1 16 16" },
62
+ { tag: "circle", cx: 5, cy: 19, r: 1.6, solid: true }
63
+ ]
64
+ };
65
+ var emailGlyph = {
66
+ kind: "functional",
67
+ shapes: [
68
+ { tag: "rect", x: 2.75, y: 4.75, width: 18.5, height: 14.5, rx: 2 },
69
+ { tag: "path", d: "m3.5 7.5 7.4 5.2a2 2 0 0 0 2.2 0l7.4-5.2" }
70
+ ]
71
+ };
72
+ var newsletterGlyph = {
73
+ kind: "functional",
74
+ shapes: [
75
+ { tag: "path", d: "M18 8.5a6 6 0 1 0-12 0c0 5-2 6.5-2 6.5h16s-2-1.5-2-6.5" },
76
+ { tag: "path", d: "M13.7 19a2 2 0 0 1-3.4 0" }
77
+ ]
78
+ };
79
+ var websiteGlyph = {
80
+ kind: "functional",
81
+ shapes: [
82
+ { tag: "circle", cx: 12, cy: 12, r: 9.25 },
83
+ { tag: "path", d: "M2.75 12h18.5" },
84
+ { tag: "path", d: "M12 2.75a14 14 0 0 1 0 18.5a14 14 0 0 1 0-18.5" }
85
+ ]
86
+ };
87
+ var socialGlyphs = {
88
+ Discord: discordGlyph,
89
+ Email: emailGlyph,
90
+ GitHub: gitHubGlyph,
91
+ Instagram: instagramGlyph,
92
+ LinkedIn: linkedInGlyph,
93
+ Newsletter: newsletterGlyph,
94
+ Rss: rssGlyph,
95
+ Website: websiteGlyph,
96
+ X: xGlyph,
97
+ YouTube: youTubeGlyph
98
+ };
99
+ var socialNames = Object.keys(socialGlyphs);
100
+ var escape = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
101
+ var attributes = (pairs) => Object.entries(pairs).map(([key, value]) => ` ${key}="${escape(String(value))}"`).join("");
102
+ var shapeMarkup = (shape) => {
103
+ const solid = { fill: "currentColor", stroke: "none" };
104
+ if (shape.tag === "path") {
105
+ return `<path${attributes({ d: shape.d, ...shape.solid ? solid : {} })}/>`;
106
+ }
107
+ if (shape.tag === "circle") {
108
+ return `<circle${attributes({
109
+ cx: shape.cx,
110
+ cy: shape.cy,
111
+ r: shape.r,
112
+ ...shape.solid ? solid : {}
113
+ })}/>`;
114
+ }
115
+ return `<rect${attributes({
116
+ x: shape.x,
117
+ y: shape.y,
118
+ width: shape.width,
119
+ height: shape.height,
120
+ rx: shape.rx
121
+ })}/>`;
122
+ };
123
+ function socialSvg(name, extra = {}) {
124
+ const glyph = socialGlyphs[name];
125
+ const base = glyph.kind === "brand" ? { fill: "currentColor" } : {
126
+ fill: "none",
127
+ stroke: "currentColor",
128
+ "stroke-width": SOCIAL_STROKE_WIDTH,
129
+ "stroke-linecap": "round",
130
+ "stroke-linejoin": "round"
131
+ };
132
+ const open = attributes({
133
+ xmlns: "http://www.w3.org/2000/svg",
134
+ viewBox: SOCIAL_VIEW_BOX,
135
+ width: "1em",
136
+ height: "1em",
137
+ ...base,
138
+ "aria-hidden": "true",
139
+ focusable: "false",
140
+ ...extra
141
+ });
142
+ return `<svg${open}>${glyph.shapes.map(shapeMarkup).join("")}</svg>`;
143
+ }
144
+
145
+ export { SOCIAL_STROKE_WIDTH, SOCIAL_VIEW_BOX, discordGlyph, emailGlyph, gitHubGlyph, instagramGlyph, linkedInGlyph, newsletterGlyph, rssGlyph, socialGlyphs, socialNames, socialSvg, websiteGlyph, xGlyph, youTubeGlyph };
@@ -1,6 +1,6 @@
1
1
  import { fins, ASSETS_PATH, poses, faces } from './chunk-CKRSQPTX.js';
2
- import { cn } from './chunk-XKYHTOUJ.js';
3
- import { naming, tagline } from './chunk-O4TAH7YJ.js';
2
+ import { cn } from './chunk-FAAGZG7A.js';
3
+ import { naming, tagline } from './chunk-FGFNK72B.js';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
 
6
6
  function Isotype({
@@ -1,5 +1,5 @@
1
1
  import { text } from './chunk-ODBFN44D.js';
2
- import { cn } from './chunk-XKYHTOUJ.js';
2
+ import { cn } from './chunk-FAAGZG7A.js';
3
3
  import { Slot } from '@radix-ui/react-slot';
4
4
  import 'class-variance-authority';
5
5
  import { jsx } from 'react/jsx-runtime';
@@ -6,7 +6,7 @@ var button = cva(
6
6
  "inline-flex cursor-pointer items-center justify-center gap-step-xs whitespace-nowrap select-none",
7
7
  "rounded-control font-sans font-medium",
8
8
  "transition-standard",
9
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
9
+ "focus-ring",
10
10
  "disabled:pointer-events-none disabled:opacity-50",
11
11
  "[&_svg]:shrink-0"
12
12
  ],
@@ -17,7 +17,10 @@ var button = cva(
17
17
  "bg-accent text-accent-on hover:bg-accent-hover",
18
18
  "light:bg-brand-hull light:text-accent-on light:hover:bg-text-secondary"
19
19
  ],
20
- conversion: "bg-warm text-warm-on hover:bg-warm-hover",
20
+ // The one control whose focus ring is not biolume. It is the system's
21
+ // only sand fill, and a biolume ring three pixels off a sand button puts
22
+ // both of the brand's accents in the same glance. See `docs/decisions/0.7.md` § 37.
23
+ conversion: "bg-warm text-warm-on hover:bg-warm-hover focus-ring-warm",
21
24
  secondary: [
22
25
  "border-hairline-hover border bg-transparent text-text-primary",
23
26
  "hover:border-accent hover:text-accent"
@@ -135,7 +138,7 @@ var CARD = [
135
138
  "group block cursor-pointer",
136
139
  CARD_SURFACE,
137
140
  CARD_HOVER,
138
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
141
+ "focus-ring"
139
142
  ];
140
143
 
141
144
  export { CARD, CARD_HOVER, CARD_SURFACE, alert, avatar, badge, button, category, metric };
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `npx arrecife doctor` — the two failures a consuming project cannot see.
4
+ *
5
+ * Both were found by hand, in the same migration, and both cost hours because
6
+ * neither produces an error. This is the check the backlog asked for.
7
+ *
8
+ * ONE · THE MISSING `@source`. Tailwind only generates the classes it can see,
9
+ * and it does not look inside `node_modules`. Without the directive every class
10
+ * the components emit is purged — `p-step-lg`, `rounded-card`, `border-hairline`
11
+ * — and they render with no padding, no radius and no border. There is no
12
+ * console error and no undefined class: the component mounts, simply undressed.
13
+ * It is in the README and it was still forgotten, because until a project uses a
14
+ * component and not just a token it does not need the line.
15
+ *
16
+ * AND THAT LAST CLAUSE IS THE CHECK, not a footnote to it. Importing the tokens
17
+ * is not the same thing as rendering a component, and the first version treated
18
+ * it as if it were: it asked `links` — which mounts no React at all, replicates
19
+ * the pieces it needs in Astro and guards the replicas with `check:replica` — for
20
+ * a directive that would have told Tailwind to generate every class in the
21
+ * library. Measured, that took its stylesheet from 16 KB to 52 KB, all of it for
22
+ * markup that does not exist in the project.
23
+ *
24
+ * So the directive is required only when the project imports something that
25
+ * BRINGS MARKUP: the root barrel, `./brand`, `./social`, `./icons`, `./form` or
26
+ * `./chart`. The portable subpaths do not — `./tokens` and `./theme` are values,
27
+ * `./variants` hands back class strings that the project then writes into its
28
+ * own files where Tailwind can see them, and `./social/data` is shapes. A
29
+ * project living entirely on those gets told the line is not needed, which is a
30
+ * different thing from not being told anything.
31
+ *
32
+ * TWO · THE TOKEN NAMESPACE COLLIDING WITH shadcn's. A project coming from
33
+ * shadcn has `@theme inline { --color-accent: var(--accent); }`, and shadcn's
34
+ * `--accent` is the hover SURFACE, `#17303E`, while this library's is the brand
35
+ * turquoise, `#35D6C0`. The result was **88 classes inside the library's own
36
+ * components** painting grey — 28 `text-accent`, 26 `outline-accent`, 15
37
+ * `bg-accent`, 12 `border-accent`. Buttons, focus rings and badges came out the
38
+ * colour of a surface, and it looked as though the migration had done nothing.
39
+ *
40
+ * Five names collide in total. Four are harmless because the two sides happen to
41
+ * agree on the value, which is why the check reports the value on each side
42
+ * instead of just the name: a collision that agrees is worth knowing about and
43
+ * is not worth failing over.
44
+ *
45
+ * WHY A COMMAND AND NOT A README PARAGRAPH. Both of these WERE a README
46
+ * paragraph. The whole reason this library exists is that a rule which is only
47
+ * written down drifts, and a rule that fails silently drifts fastest.
48
+ */
49
+ import { readdir, readFile } from 'node:fs/promises';
50
+ import { dirname, join, relative, resolve } from 'node:path';
51
+ import { fileURLToPath } from 'node:url';
52
+
53
+ const PACKAGE = '@eduardoalvarez/arrecife';
54
+ const here = dirname(fileURLToPath(import.meta.url));
55
+ const cwd = process.cwd();
56
+
57
+ const SKIP = new Set(['node_modules', '.git', '.next', 'dist', 'build', 'out', '.astro', 'coverage']);
58
+
59
+ async function* walk(dir, matches) {
60
+ let items;
61
+ try {
62
+ items = await readdir(dir, { withFileTypes: true });
63
+ } catch {
64
+ return;
65
+ }
66
+ for (const item of items) {
67
+ if (item.name.startsWith('.') && item.name !== '.') continue;
68
+ const path = join(dir, item.name);
69
+ if (item.isDirectory()) {
70
+ if (SKIP.has(item.name)) continue;
71
+ yield* walk(path, matches);
72
+ } else if (matches(item.name)) {
73
+ yield path;
74
+ }
75
+ }
76
+ }
77
+
78
+ const css = (dir) => walk(dir, (name) => name.endsWith('.css'));
79
+
80
+ /** Everything a bundler would look at for an import, plus the templates. */
81
+ const CODE = /\.(?:[cm]?[jt]sx?|astro|vue|svelte|mdx?)$/;
82
+
83
+ /**
84
+ * The subpaths that carry no markup of ours, so importing one needs no `@source`.
85
+ *
86
+ * `./variants` is the one worth spelling out: it DOES hand back Tailwind classes,
87
+ * and they still need no directive, because the project writes them into its own
88
+ * files — `class={buttonVariants({ variant: 'tertiary' })}` in an `.astro` — and
89
+ * that is a file Tailwind already scans. What `@source` buys is visibility into
90
+ * `node_modules`, and nothing of ours renders there in this case.
91
+ */
92
+ const CLASS_FREE = new Set([
93
+ `${PACKAGE}/tokens`,
94
+ `${PACKAGE}/tokens/theme.css`,
95
+ `${PACKAGE}/theme`,
96
+ `${PACKAGE}/variants`,
97
+ `${PACKAGE}/social/data`,
98
+ `${PACKAGE}/og`,
99
+ `${PACKAGE}/shiki`,
100
+ `${PACKAGE}/llms.txt`,
101
+ `${PACKAGE}/package.json`,
102
+ ]);
103
+
104
+ /** `from "x"`, `import "x"`, `require("x")` — the same net `check-package-exports` casts. */
105
+ const SPECIFIERS = /(?:\bfrom\s*|\bimport\s*|\brequire\(\s*)['"]([^'"]+)['"]/g;
106
+
107
+ /**
108
+ * The first import of ours that brings markup with it, or `null`.
109
+ *
110
+ * It returns the import and not a boolean so the message can name it. «Add an
111
+ * `@source`» is an instruction; «you import `Button` from the root, so add an
112
+ * `@source`» is an explanation, and the difference decides whether somebody
113
+ * pastes the line or understands when they need it.
114
+ */
115
+ async function firstRendering() {
116
+ for await (const file of walk(cwd, (name) => CODE.test(name))) {
117
+ const source = await readFile(file, 'utf8');
118
+ for (const [, specifier] of source.matchAll(SPECIFIERS)) {
119
+ if (specifier !== PACKAGE && !specifier.startsWith(`${PACKAGE}/`)) continue;
120
+ if (CLASS_FREE.has(specifier)) continue;
121
+ if (specifier.startsWith(`${PACKAGE}/assets/`)) continue;
122
+ return { file: relative(cwd, file), specifier };
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ /** `--name: value;` pairs inside every `@theme` block of a stylesheet. */
129
+ function themeProperties(source) {
130
+ const found = new Map();
131
+ // `@theme`, `@theme inline`, `@theme static` — the modifier does not matter
132
+ // for a name collision, only the name does.
133
+ const blocks = source.matchAll(/@theme[^{]*\{([\s\S]*?)\n\}/g);
134
+ for (const [, body] of blocks) {
135
+ for (const [, name, value] of body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) {
136
+ found.set(name, value.trim());
137
+ }
138
+ }
139
+ return found;
140
+ }
141
+
142
+ /** Our own published properties, read from the package rather than hardcoded. */
143
+ async function ours() {
144
+ for (const candidate of [
145
+ resolve(here, 'tokens/theme.css'), // published: dist/doctor.mjs beside dist/tokens/
146
+ resolve(here, '../dist/tokens/theme.css'), // in this repo, run from scripts/
147
+ ]) {
148
+ try {
149
+ return themeProperties(await readFile(candidate, 'utf8'));
150
+ } catch {
151
+ /* try the next one */
152
+ }
153
+ }
154
+ return new Map();
155
+ }
156
+
157
+ const problems = [];
158
+ const notes = [];
159
+
160
+ const theirs = await ours();
161
+ if (theirs.size === 0) {
162
+ console.error(`arrecife · could not read ${PACKAGE}'s own theme.css. Run \`pnpm build\` first.`);
163
+ process.exit(1);
164
+ }
165
+
166
+ let entries = 0;
167
+
168
+ /*
169
+ Worked out BEFORE the stylesheets are read, and once: the answer is a fact
170
+ about the project and not about the sheet, and the same sheet must not get two
171
+ different verdicts because of the order the walk happened to take.
172
+ */
173
+ const rendering = await firstRendering();
174
+
175
+ for await (const file of css(cwd)) {
176
+ const source = await readFile(file, 'utf8');
177
+ const shown = relative(cwd, file);
178
+
179
+ // The entry sheet is the one that imports Tailwind. A partial that only holds
180
+ // an `@theme` block is checked for collisions all the same.
181
+ const isEntry = /@import\s+["']tailwindcss["']/.test(source);
182
+ const importsTokens = source.includes(`${PACKAGE}/tokens/theme.css`);
183
+ const hasSource = /@source\s+["'][^"']*@eduardoalvarez\/arrecife/.test(source);
184
+
185
+ if (isEntry) entries += 1;
186
+
187
+ if (isEntry && importsTokens && !hasSource) {
188
+ if (rendering) {
189
+ // The path is relative to the SHEET, not to the project root, which is the
190
+ // part that gets written wrong: a sheet in `src/styles/` goes up three.
191
+ const target = relative(dirname(file), join(cwd, 'node_modules', PACKAGE, 'dist'));
192
+ problems.push(
193
+ `${shown}\n` +
194
+ ` imports ${PACKAGE}/tokens/theme.css and has no @source.\n` +
195
+ ` ${rendering.file} imports ${rendering.specifier}, which renders our markup,\n` +
196
+ ` so every class those components emit is being purged — silently. Add:\n\n` +
197
+ ` @source "${target.split('\\\\').join('/')}";\n`,
198
+ );
199
+ } else {
200
+ notes.push(
201
+ `${shown} imports the tokens and has no @source, and does not need one: ` +
202
+ `nothing in this project imports a subpath that renders our markup. ` +
203
+ `Adding it would make Tailwind generate the whole library for markup that is not here.`,
204
+ );
205
+ }
206
+ }
207
+
208
+ for (const [name, value] of themeProperties(source)) {
209
+ const mine = theirs.get(name);
210
+ if (mine === undefined) continue;
211
+
212
+ // A value that points at another property cannot be compared here, and it is
213
+ // exactly the shape the shadcn collision takes.
214
+ const indirect = value.includes('var(');
215
+ const same = !indirect && value.toLowerCase() === mine.toLowerCase();
216
+
217
+ if (same) {
218
+ notes.push(`${shown} redefines ${name}, with the same value (${value}). Harmless.`);
219
+ continue;
220
+ }
221
+
222
+ problems.push(
223
+ `${shown}\n` +
224
+ ` redefines ${name}, which ${PACKAGE} owns.\n` +
225
+ ` yours: ${value}${indirect ? ' ← points at another property, so it wins silently' : ''}\n` +
226
+ ` arrecife: ${mine}\n` +
227
+ ` Every class in the library that uses it takes YOUR value, including the\n` +
228
+ ` ones inside the library's own components.\n`,
229
+ );
230
+ }
231
+ }
232
+
233
+ if (entries === 0) {
234
+ console.log('arrecife · no stylesheet importing tailwindcss was found. Nothing to check.');
235
+ process.exit(0);
236
+ }
237
+
238
+ for (const note of notes) console.log(`arrecife · note · ${note}`);
239
+
240
+ if (problems.length > 0) {
241
+ console.error(`\narrecife · ${problems.length} thing(s) that fail without saying so:\n`);
242
+ for (const problem of problems) console.error(` ${problem}`);
243
+ process.exit(1);
244
+ }
245
+
246
+ console.log(
247
+ `arrecife · ${entries} stylesheet(s) checked · no class is being purged and no token is redefined`,
248
+ );
@@ -67,7 +67,7 @@ var typeScale = {
67
67
  * (plankton 5.57:1 over abyss), which is the part that is not negotiable.
68
68
  *
69
69
  * At 13 the three badge families grew past the size of a small button and
70
- * outweighed the title they accompany. See `docs/decisions.md`.
70
+ * outweighed the title they accompany. See `docs/decisions/`.
71
71
  */
72
72
  chip: { family: "mono", size: 11.5, lineHeight: 1.4, weight: 400 },
73
73
  /**
@@ -113,7 +113,7 @@ var control = {
113
113
  * baseline; 28 would have been a fifth height that matches nothing.
114
114
  *
115
115
  * It does not replace `icon`. A page's primary action stays at 42; this is for
116
- * a row of a table. See `docs/decisions.md` § 22.
116
+ * a row of a table. See `docs/decisions/0.6.md` § 22.
117
117
  */
118
118
  iconSm: 32
119
119
  };
@@ -3,7 +3,7 @@ import { ComponentPropsWithoutRef } from 'react';
3
3
  import * as react_hook_form from 'react-hook-form';
4
4
  import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
5
5
  import { Slot } from '@radix-ui/react-slot';
6
- import { L as Label } from '../label-MgHFKnFy.cjs';
6
+ import { L as Label } from '../label-DJ4HuD-R.cjs';
7
7
  import '@radix-ui/react-label';
8
8
 
9
9
  /**
@@ -3,7 +3,7 @@ import { ComponentPropsWithoutRef } from 'react';
3
3
  import * as react_hook_form from 'react-hook-form';
4
4
  import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
5
5
  import { Slot } from '@radix-ui/react-slot';
6
- import { L as Label } from '../label-MgHFKnFy.js';
6
+ import { L as Label } from '../label-DJ4HuD-R.js';
7
7
  import '@radix-ui/react-label';
8
8
 
9
9
  /**
@@ -1,9 +1,9 @@
1
1
  'use client';
2
- import { Label } from '../chunk-DKCN7BAL.js';
3
- import { Text } from '../chunk-JMOOFZ3B.js';
2
+ import { Label } from '../chunk-6IGD5REB.js';
3
+ import { Text } from '../chunk-TRPBID2W.js';
4
4
  import '../chunk-ODBFN44D.js';
5
- import { cn } from '../chunk-XKYHTOUJ.js';
6
- import '../chunk-O4TAH7YJ.js';
5
+ import { cn } from '../chunk-FAAGZG7A.js';
6
+ import '../chunk-FGFNK72B.js';
7
7
  import '../chunk-MLKGABMK.js';
8
8
  import { createContext, useContext, useId } from 'react';
9
9
  import { FormProvider, Controller, useFormContext, useFormState } from 'react-hook-form';