@eduardoalvarez/arrecife 0.9.0 → 0.11.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 (41) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +111 -70
  3. package/dist/brand/index.cjs +9 -3
  4. package/dist/brand/index.d.cts +45 -6
  5. package/dist/brand/index.d.ts +45 -6
  6. package/dist/brand/index.js +2 -3
  7. package/dist/chart/index.cjs +26 -4
  8. package/dist/chart/index.d.cts +19 -3
  9. package/dist/chart/index.d.ts +19 -3
  10. package/dist/chart/index.js +26 -5
  11. package/dist/{chunk-MPZBF2TZ.js → chunk-RKGKO2TW.js} +11 -5
  12. package/dist/chunk-ZSCSKCTY.js +26 -0
  13. package/dist/form/index.js +0 -1
  14. package/dist/icons/index.d.cts +19 -10
  15. package/dist/icons/index.d.ts +19 -10
  16. package/dist/icons/index.js +2 -27
  17. package/dist/index.cjs +277 -401
  18. package/dist/index.d.cts +114 -13
  19. package/dist/index.d.ts +114 -13
  20. package/dist/index.js +211 -203
  21. package/dist/og/index.js +0 -1
  22. package/dist/shiki/index.js +0 -1
  23. package/dist/theme/index.js +0 -1
  24. package/dist/tokens/index.js +0 -1
  25. package/dist/tokens/theme.css +21 -1
  26. package/dist/variants/index.js +0 -1
  27. package/llms.txt +99 -138
  28. package/package.json +18 -30
  29. package/dist/chunk-HOADZ6GS.js +0 -72
  30. package/dist/chunk-LXRGQKMG.js +0 -145
  31. package/dist/chunk-MLKGABMK.js +0 -7
  32. package/dist/index-BbRplw_B.d.cts +0 -58
  33. package/dist/index-BbRplw_B.d.ts +0 -58
  34. package/dist/social/data.cjs +0 -161
  35. package/dist/social/data.d.cts +0 -161
  36. package/dist/social/data.d.ts +0 -161
  37. package/dist/social/data.js +0 -2
  38. package/dist/social/index.cjs +0 -153
  39. package/dist/social/index.d.cts +0 -2
  40. package/dist/social/index.d.ts +0 -2
  41. package/dist/social/index.js +0 -3
package/dist/og/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { ASSETS_PATH, faces, poses, fins } from '../chunk-CKRSQPTX.js';
2
2
  import { fonts, dark, light, naming, gradient, tagline, typeScale } from '../chunk-FGFNK72B.js';
3
- import '../chunk-MLKGABMK.js';
4
3
 
5
4
  // src/og/templates.ts
6
5
  var OG = {
@@ -1,5 +1,4 @@
1
1
  import { syntax } from '../chunk-FGFNK72B.js';
2
- import '../chunk-MLKGABMK.js';
3
2
 
4
3
  // src/shiki/theme.ts
5
4
  var arrecife = {
@@ -1,2 +1 @@
1
1
  export { THEME_ATTRIBUTE, THEME_EVENT, THEME_KEY, applyTheme, currentTheme, preferredTheme, storedTheme, themeScript, toggleTheme, watchTheme } from '../chunk-GCRII2KQ.js';
2
- import '../chunk-MLKGABMK.js';
@@ -1,3 +1,2 @@
1
1
  export { tokens } from '../chunk-5A5GH2PF.js';
2
2
  export { brand, colors, control, dark, fonts, gradient, light, limits, motion, naming, radius, series, shadow, size, spacing, syntax, tagline, typeScale } from '../chunk-FGFNK72B.js';
3
- import '../chunk-MLKGABMK.js';
@@ -379,7 +379,22 @@
379
379
  focus-ring-warm exists for the ONE control the ring cannot be biolume on:
380
380
  the conversion button is the system's only sand fill, and a biolume ring
381
381
  around it puts both of the brand's accents in the same three pixels. It sets
382
- only the color, so the width and the offset stay in one place. */
382
+ only the color, so the width and the offset stay in one place.
383
+
384
+ focus-ring-inset flips the OFFSET, and only the offset, for the same reason
385
+ and by the same rule: an element that is focusable but sits inside a clipped
386
+ container cannot wear a ring drawn 3px outside itself, because the container
387
+ paints it away. CodeBlock is the case — its root carries overflow-hidden so
388
+ its rounded corners hold, and its pre scrolls, so the pre has to be focusable.
389
+ With the outward ring, three of its four sides were clipped and what was left
390
+ read as a stray line under the header: a focus indicator that is present in
391
+ the CSS and invisible on screen, which is WCAG 2.4.7 failing while looking
392
+ fixed.
393
+
394
+ It is -3px and not 0: at 0 the ring sits exactly on the border box and reads
395
+ as a change of border rather than as a ring. The padding it eats into is
396
+ p-step-md, so it never touches the text. Reach for it ONLY when a clipping
397
+ ancestor is the reason — not because the outward ring looks too loud. */
383
398
  @utility focus-ring {
384
399
  &:focus-visible {
385
400
  outline: 2px solid var(--color-accent);
@@ -391,6 +406,11 @@
391
406
  outline-color: var(--color-warm);
392
407
  }
393
408
  }
409
+ @utility focus-ring-inset {
410
+ &:focus-visible {
411
+ outline-offset: -3px;
412
+ }
413
+ }
394
414
 
395
415
  /* The system's only transition, as a utility. States are communicated with
396
416
  border and color, not with movement: this class cannot animate anything else. */
@@ -1,3 +1,2 @@
1
1
  export { CARD, CARD_HOVER, CARD_SURFACE, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, category as categoryBadgeVariants, metric as metricBadgeVariants } from '../chunk-XXDATT3A.js';
2
2
  export { text as textVariants } from '../chunk-ODBFN44D.js';
3
- import '../chunk-MLKGABMK.js';
package/llms.txt CHANGED
@@ -34,6 +34,7 @@ Requirements, and they are not optional:
34
34
  | | |
35
35
  | --- | --- |
36
36
  | React | `^19.0.0` and `react-dom` `^19.0.0`, as peer dependencies |
37
+ | Phosphor | `@phosphor-icons/react` `^2.1.0`, as a peer dependency. **Required since 0.10.0**: every icon the library draws comes from it |
37
38
  | Tailwind | v4. **There is no v3 preset**: the output is `@theme`, which v3 does not understand |
38
39
  | Node | `>=22.18.0` for the subpaths that run at build time (`./og`, `./tokens`) |
39
40
 
@@ -41,8 +42,15 @@ Radix, `clsx`, `tailwind-merge`, `class-variance-authority`, `date-fns` and
41
42
  `react-day-picker` come as dependencies of the library. You do not need to
42
43
  install or declare them.
43
44
 
44
- **It ships no icon set.** The glyphs the components
45
- need are inline, inherit `currentColor` and measure 1em.
45
+ **It ships no icon INVENTORY, and it does ship the drawing.** Until 0.10.0 the
46
+ components carried a hand-drawn set of their own; they now draw every glyph from
47
+ `@phosphor-icons/react` through `Icon`, which fixes the size at 1em and takes the
48
+ weight from `tone`. Which icons your project uses is still your project's
49
+ decision — what stopped being the project's is the line they are drawn with.
50
+
51
+ That is why Phosphor is a required peer dependency rather than an optional one:
52
+ the `Alert`, the `Select` and the `Button` you import already draw with it,
53
+ whether or not you draw an icon yourself.
46
54
 
47
55
  ## Tailwind configuration
48
56
 
@@ -116,8 +124,7 @@ that only whoever uses them installs.
116
124
  | `@eduardoalvarez/arrecife/og` | **no** | — | The Open Graph templates for Satori |
117
125
  | `@eduardoalvarez/arrecife/shiki` | **no** | — | The syntax highlighting theme |
118
126
  | `@eduardoalvarez/arrecife/brand` | yes | — | Logo, isotype and mascot as components |
119
- | `@eduardoalvarez/arrecife/social` | yes, on the server only | — | The ten social icons, loose. No `"use client"` |
120
- | `@eduardoalvarez/arrecife/social/data` | **no** | — | The same ten as shapes, plus `socialSvg`. For a template that mounts no React |
127
+ | `@eduardoalvarez/arrecife/icons` | yes, on the server only | `@phosphor-icons/react` | `Icon`, the wrapper that fixes size and weight. No `"use client"` |
121
128
  | `@eduardoalvarez/arrecife/icons` | yes | `@phosphor-icons/react` | `Icon`, which draws a Phosphor icon at the system's size, and at the weight its role asks for |
122
129
  | `@eduardoalvarez/arrecife/form` | yes | `react-hook-form` | The form layer: labels, errors and `aria-*` |
123
130
  | `@eduardoalvarez/arrecife/chart` | yes | `recharts` | The chart chassis, the series palette and the three chart types |
@@ -148,12 +155,12 @@ before 0.6.0 and it pulled 272 KB of client chunk in for components that never
148
155
  needed it.
149
156
 
150
157
  The six portable subpaths do NOT carry the directive, and that is the half that
151
- matters in a Server Component: `./tokens`, `./theme`, `./variants`, `./social/data`,
152
- `./og` and `./shiki` stay on the server. Neither does `./social`, which is a third
153
- case: it renders React — it is ten `<svg>` — so it can never be portable, but it
154
- holds no state and nothing about it needs a client boundary. It is the only way to put a
155
- social icon in a Server Component, and § «The social icons come from `./social`»
156
- below says why the grouped form cannot do it. If all you need are classes — for a `<div>`, an
158
+ matters in a Server Component: `./tokens`, `./theme`, `./variants`,
159
+ `./og` and `./shiki` stay on the server. Neither does `./icons`, which is a third
160
+ case: it renders React — it is one `<svg>` — so it can never be portable, but it
161
+ holds no state and nothing about it needs a client boundary. It is how you put an
162
+ icon in a Server Component, and § «The social icons are yours, and they come from
163
+ Phosphor» below says which Phosphor entry to pair it with there. If all you need are classes — for a `<div>`, an
157
164
  `<a>` or an Astro island you do not want to hydrate — import them from
158
165
  `./variants` and nothing crosses to the client:
159
166
 
@@ -373,8 +380,9 @@ component's.
373
380
  <EmptyState variant="inline" expression="waiting" title="…" />
374
381
  ```
375
382
 
376
- `inline` takes an optional `icon` — a `ReactNode` the project passes and sizes,
377
- at 1em and in `currentColor`, like `Stat`'s. The library ships no icons.
383
+ `inline` takes an optional `icon` — a `ReactNode` the project passes, normally
384
+ `<Icon as={…} />` from `./icons`, which is already 1em and `currentColor`. The
385
+ library ships no icon inventory: which glyph goes there is your decision.
378
386
 
379
387
  Do not reach for `page` inside a table because the face is «nicer»: an admin
380
388
  screen with a dozen empty regions gets a dozen mascots, which is what made every
@@ -383,7 +391,10 @@ consuming project write its own empty state instead of using this one.
383
391
  ### The two shapes of `Footer`
384
392
 
385
393
  ```tsx
386
- // The default. Stacked rows, signature level with the first one.
394
+ // The default. Stacked rows, signature level with the first one — and stacked
395
+ // and CENTRED below `sm`, which is what a phone gets.
396
+ // SOCIAL is the project's own array: `{ label, href, icon }`, the icon drawn
397
+ // with `<Icon as={GithubLogo} tone="current" />`.
387
398
  <Footer brand={<Logo />} social={SOCIAL} />
388
399
 
389
400
  // `full`. Brand and description on the left, link columns on the right,
@@ -469,6 +480,13 @@ re-exported, because they are unchanged and wrapping them buys nothing.
469
480
  down for a ranking. Recharts calls that same thing `layout="vertical"` — if you
470
481
  are porting code, the value flips.
471
482
 
483
+ **A percentage passes `valueMax={100}`.** Without it the value axis ends at the
484
+ largest datum, and on a horizontal ranking — whose value axis is hidden — a
485
+ course watched to 40 % draws as a full bar when it is the highest on the list.
486
+ The bottom is always zero, and it is a floor rather than a clip: a datum above
487
+ `valueMax` widens the axis instead of running off the edge. It is on all three
488
+ types. See `decisions/0.11.md` § 58.
489
+
472
490
  `stacked` on `AreaChart` and `BarChart` adds the series up. Without it areas
473
491
  overlap, which is honest and rarely what you want with more than one series: to
474
492
  COMPARE rather than add up, the type is `LineChart`.
@@ -476,63 +494,50 @@ COMPARE rather than add up, the type is `LineChart`.
476
494
  Anything that is not a series over a category axis has no type and is not missing
477
495
  one. A doughnut is `ChartContainer` plus Recharts' `Pie` with `SERIES_COLORS`.
478
496
 
479
- ### The social icons come from `./social`
497
+ ### The social icons are yours, and they come from Phosphor
498
+
499
+ Until 0.10.0 the library shipped ten of them at `./social` — `GitHub`,
500
+ `LinkedIn`, `X`, `Instagram`, `Discord`, `YouTube`, `Rss`, `Email`,
501
+ `Newsletter`, `Website` — drawn by hand. That subpath is gone. Phosphor has all
502
+ ten and the migration is one import per call site:
480
503
 
481
504
  ```tsx
482
- // ❌ does not exist: the root publishes them grouped, not loose
483
- import { GitHub } from '@eduardoalvarez/arrecife';
505
+ // ❌ removed in 0.10.0
506
+ import { GitHub, LinkedIn, Website } from '@eduardoalvarez/arrecife/social';
484
507
 
485
- // ✅ the normal form
486
- import { GitHub } from '@eduardoalvarez/arrecife/social';
508
+ // ✅
509
+ import { GithubLogo, LinkedinLogo, Globe } from '@phosphor-icons/react';
510
+ import { Icon } from '@eduardoalvarez/arrecife/icons';
487
511
 
488
- // ✅ for iterating the catalogue
489
- import { social } from '@eduardoalvarez/arrecife';
490
- <social.GitHub />
512
+ <Icon as={GithubLogo} tone="current" />
491
513
  ```
492
514
 
493
- All ten: `GitHub`, `LinkedIn`, `X`, `Instagram`, `Discord`, `YouTube`, `Rss`,
494
- `Email`, `Newsletter`, `Website`. `Newsletter` is the bell: a way to follow, like
495
- `Rss`, named for what it means. `Website` is «my other site» — the personal
496
- domain in a footer full of networks — and it is what replaces borrowing a globe
497
- from an icon set, which brings its own stroke weight and its own margins.
498
-
499
- Six are brands and go SOLID, four are functional and use a 1.6 stroke. A row that
500
- mixes the two pens is the normal case, not a defect: a brand is somebody else's
501
- silhouette and cannot be outlined, and a symbol the system draws itself has no
502
- owner to be faithful to.
503
-
504
- **In a Server Component the subpath is mandatory, not preferred.** The root
505
- carries `"use client"`, and a client reference crosses the boundary per EXPORT —
506
- the properties of a plain object are not exports, so `social.LinkedIn` is
507
- `undefined` on the server and `undefined` as an element type kills the build at
508
- prerender. `./social` carries no directive: it renders on the server and ships no
509
- client JS. Use `social` only when mapping a list of names onto icons.
510
-
511
- The root keeps the group because one of them is called `X`, and loose at the root
512
- it collides. In the subpath, alias it: `import { X as XIcon }`.
513
-
514
- **If you mount no React, the shapes are published too.** `./social/data` imports
515
- nothing, so an `.astro` that ships no framework JavaScript can draw the same
516
- glyph instead of pasting the `<path>` into the project:
517
-
518
- ```astro
519
- ---
520
- import { socialSvg } from '@eduardoalvarez/arrecife/social/data';
521
- ---
522
- <Fragment set:html={socialSvg('GitHub', { class: 'size-[19px]' })} />
523
- ```
524
-
525
- `socialGlyphs` is the catalogue keyed by name, `socialNames` is the ten names in
526
- order, and every glyph is exported on its own — `gitHubGlyph`, `websiteGlyph` —
527
- if you want the shapes rather than the markup. The React components are drawn
528
- from that same file, so the two renderings cannot disagree. Reaching for
529
- `socialGlyphs` or `socialSvg` names all ten, which is the price of iterating a
530
- catalogue; `import { LinkedIn }` still costs one shape.
531
-
532
- The internal glyphs — `Close`, `ChevronDown`, `Sun` — are **not exported** and
533
- are not going to be: they are the primitives' minimum set. A component that needs
534
- an icon receives it as a prop (`Stat` has `icon`, each `SocialLink` in `Footer`
535
- has its own). Do not ask for them to be published: pass your own.
515
+ | Removed | Phosphor | `tone` |
516
+ | --- | --- | --- |
517
+ | `GitHub` | `GithubLogo` | `current` |
518
+ | `LinkedIn` | `LinkedinLogo` | `current` |
519
+ | `X` | `XLogo` | `current` |
520
+ | `Instagram` | `InstagramLogo` | `current` |
521
+ | `Discord` | `DiscordLogo` | `current` |
522
+ | `YouTube` | `YoutubeLogo` | `current` |
523
+ | `Rss` | `Rss` | `action` |
524
+ | `Email` | `Envelope` | `action` |
525
+ | `Newsletter` | `BellSimple` | `action` |
526
+ | `Website` | `Globe` | `action` |
527
+
528
+ The `tone` column IS the old drawing rule, written on the axis `Icon` already
529
+ has. Six are brands and go SOLID, which is `tone="current"` — Phosphor's `fill`.
530
+ Four are functional and keep the default `action`, which is the system's line. A
531
+ row that mixes the two pens is the normal case, not a defect: a brand is somebody
532
+ else's silhouette and cannot be outlined, and a symbol has no owner to be
533
+ faithful to.
534
+
535
+ **In a Server Component import `Icon` from `./icons` and the glyph from
536
+ `@phosphor-icons/react/ssr`.** The root of this library carries `"use client"`;
537
+ `./icons` deliberately does not, so an icon renders on the server and ships no
538
+ client JS. Phosphor's default build reads `IconContext` through `useContext`,
539
+ which throws in a Server Component — the `/ssr` entry is the same icons without
540
+ that read.
536
541
 
537
542
  ## Tokens
538
543
 
@@ -592,6 +597,10 @@ compiles and looks wrong, or that fails the project's accessibility audit.
592
597
  the only member of the second criterion § 23 opened for it.
593
598
  6. **Semantics and scale are independent.** An `h2` that has to look small is
594
599
  `<Text as="h2" variant="h3">`, never an `h3` that lies about the hierarchy.
600
+ `PageHeader` makes the same split: `as` is the level and `titleVariant` the
601
+ scale. An admin panel's title is `<PageHeader title="Ventas"
602
+ titleVariant="h3" />` — still the page's only `h1`, at 25px instead of 44. See
603
+ `decisions/0.11.md` § 57.
595
604
  7. **`textMuted` never goes over `surfaceRaised`**: it gives 4.07 in dark. Over a
596
605
  raised surface — menus, active tabs — the token is `textSecondary`.
597
606
  8. **A background tinted with a semantic color carries text from a text token**,
@@ -612,6 +621,11 @@ compiles and looks wrong, or that fails the project's accessibility audit.
612
621
  not the humour contract. See `decisions/0.7.md` § 27.
613
622
  12. **The fin is not a free parameter**: `foam` on a dark background, `color` on a
614
623
  light one. The components already choose it from the background.
624
+ **On a site that switches theme, pass `background="auto"`** to `Isotype` or
625
+ `Logo`: both fins are rendered and the `light:` variant shows the one that
626
+ reads, so no call site has to know the theme. A surface that keeps one mode
627
+ whatever the page does — a dark panel on a light page — is a fixed
628
+ background, and it still says `dark`. See `decisions/0.11.md` § 60.
615
629
 
616
630
  ## What the library does NOT do, on purpose
617
631
 
@@ -627,6 +641,12 @@ These are the confusions people run into most often when consuming it.
627
641
  provider.
628
642
  - **It ships no router.** The components with links accept `asChild` to wrap the
629
643
  framework's `Link`.
644
+ - **It does not know your prices, ratings or images.** `CourseCard` takes
645
+ `media` — the cover, bleeding to the edges, with `alt=""` because the title
646
+ already names the card — and `footer`, the closing row where a project puts the
647
+ rating and the price. Both are nodes the project draws; the card keeps the
648
+ title, its hover and the sand progress bar. The title does not go over the
649
+ cover. See `decisions/0.11.md` § 59.
630
650
  - **It ships no `data-testid`.** A composed part your test suite has to reach is
631
651
  reached with a slot: `ArticleCard`'s `tagAsChild`, `Breadcrumb`'s and
632
652
  `TableOfContents`'s `linkAsChild`. They hand you the element and its
@@ -771,7 +791,7 @@ Source: `src/primitives/alert.tsx`
771
791
  | prop | type | req. | default | what it does |
772
792
  | --- | --- | --- | --- | --- |
773
793
  | `emphasis` | `"subtle" \| "strong"` | | | |
774
- | `icon` | `ReactNode` | | | Replaces the variant's mono glyph. Never an emoji: if you need something else, it is an SVG from `glyphs`. |
794
+ | `icon` | `ReactNode` | | | Replaces the variant's glyph. Never an emoji: if you need something else, it is `<Icon as={…} />` from `@eduardoalvarez/arrecife/icons`. |
775
795
  | `title` | `ReactNode` | | | |
776
796
  | `variant` | `"accent" \| "success" \| "warning" \| "error"` | | | |
777
797
 
@@ -1391,11 +1411,15 @@ Source: `src/components/code-block/index.tsx`
1391
1411
 
1392
1412
  Source: `src/components/course-card/index.tsx`
1393
1413
 
1394
- - Extends: `Omit<CardShellProps, 'children' \| 'title'>`
1414
+ The course, as a card that links to it.
1415
+
1416
+ - Extends: `Omit<CardShellProps, 'children' \| 'title' \| 'media'>`
1395
1417
 
1396
1418
  | prop | type | req. | default | what it does |
1397
1419
  | --- | --- | --- | --- | --- |
1398
1420
  | `asChild` | `boolean \| undefined` | | | Renders the child instead of an `<a>`. It is how Next's or Astro's `Link` plugs in without the library depending on any router. |
1421
+ | `footer` | `ReactNode` | | | The closing row, under `meta`: the rating, the price, whatever the project sells the course with. It sits at the bottom of the card, so the rows of a grid line up whatever the length of each summary. |
1422
+ | `media` | `ReactNode` | | | The cover, at the top and bleeding to the card's edges, with `alt=""`: the whole card is one link and `title` already names it. |
1399
1423
  | `meta` | `readonly ReactNode[]` | | | Level, duration, number of lessons: whatever the project wants to list. |
1400
1424
  | `progress` | `number` | | | Percentage completed. It only makes sense for someone already enrolled; when passed, the bar goes in sand, which is the color of course progress. |
1401
1425
  | `status` | `ReactNode` | | | Status label: «próximamente», «gratis», «nuevo». |
@@ -1559,6 +1583,7 @@ One header at two scales, not two components.
1559
1583
  | `eyebrow` | `ReactNode` | | | Mono, small caps, in accent. It is the section the page belongs to. |
1560
1584
  | `size` | `"display" \| "page"` | | `page` | |
1561
1585
  | `title` | `ReactNode` | yes | | |
1586
+ | `titleVariant` | `"display" \| "h1" \| "h2" \| "h3"` | | | The headline's scale, when the screen needs a different one from what `size` gives — `display` for `display`, `h1` for `page`. |
1562
1587
 
1563
1588
  ### ScrollingProgressBar
1564
1589
 
@@ -1647,12 +1672,14 @@ Imported from `@eduardoalvarez/arrecife` or `@eduardoalvarez/arrecife/brand`. 4
1647
1672
 
1648
1673
  Source: `src/brand/isotype.tsx`
1649
1674
 
1675
+ The fin, in the variant its background asks for.
1676
+
1650
1677
  - Extends: `Omit<ComponentPropsWithoutRef<'img'>, 'src' \| 'alt'>`
1651
1678
 
1652
1679
  | prop | type | req. | default | what it does |
1653
1680
  | --- | --- | --- | --- | --- |
1654
1681
  | `alt` | `string` | | | Alt text. Empty when the isotype accompanies text that already names it. |
1655
- | `background` | `"dark" \| "light"` | | `dark` | Which background it sits on. Deciding is mandatory even though it has a default: the fin's body is nearly black, so the two-blue variant disappears over abyss. Being a prop, the rule stops being something to remember. |
1682
+ | `background` | `"dark" \| "light" \| "auto"` | | `dark` | Which background it sits on, or `auto` on a site that switches theme: both fins are rendered and CSS shows the one that reads. Deciding is mandatory even though it has a default: the fin's body is nearly black, so the two-blue variant disappears over abyss. |
1656
1683
  | `basePath` | `string` | | `ASSETS_PATH` | |
1657
1684
 
1658
1685
  ### Logo
@@ -1665,7 +1692,7 @@ The wordmark comes from `naming.wordmark`, not from a hand-written string, and i
1665
1692
 
1666
1693
  | prop | type | req. | default | what it does |
1667
1694
  | --- | --- | --- | --- | --- |
1668
- | `background` | `"dark" \| "light"` | | `dark` | |
1695
+ | `background` | `"dark" \| "light" \| "auto"` | | `dark` | The background the logo sits on, handed to its fin. `auto` follows the theme — see `Isotype`. The wordmark needs no help: it is `textPrimary`, which already follows the mode. |
1669
1696
  | `basePath` | `string` | | `ASSETS_PATH` | |
1670
1697
  | `isotypeOnly` | `boolean \| undefined` | | `false` | Hides the wordmark and leaves only the fin, for very narrow bars. |
1671
1698
  | `withTagline` | `boolean \| undefined` | | `false` | Adds the tagline under the wordmark, separated from the fin by a divider. |
@@ -1696,48 +1723,6 @@ Tiburoncín's head, with an expression.
1696
1723
  | `basePath` | `string` | | `ASSETS_PATH` | |
1697
1724
  | `expression` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | yes | | |
1698
1725
 
1699
- ## Social icons
1700
-
1701
- Imported from `@eduardoalvarez/arrecife/social` · or grouped as `social` from the root. 10 exports.
1702
-
1703
- ### GitHub, LinkedIn, X, Instagram, Discord, YouTube, Rss, Email, Newsletter, Website
1704
-
1705
- Source: `src/social/index.tsx`
1706
-
1707
- **GitHub**
1708
- - No own props: it passes through those of the element or primitive it wraps.
1709
-
1710
- **LinkedIn**
1711
- - No own props: it passes through those of the element or primitive it wraps.
1712
-
1713
- **X**
1714
- - No own props: it passes through those of the element or primitive it wraps.
1715
-
1716
- **Instagram**
1717
- - No own props: it passes through those of the element or primitive it wraps.
1718
-
1719
- **Discord**
1720
- - No own props: it passes through those of the element or primitive it wraps.
1721
-
1722
- **YouTube**
1723
- - No own props: it passes through those of the element or primitive it wraps.
1724
-
1725
- **Rss**
1726
- - No own props: it passes through those of the element or primitive it wraps.
1727
-
1728
- **Email**
1729
- - No own props: it passes through those of the element or primitive it wraps.
1730
-
1731
- **Newsletter**
1732
- The newsletter. It plays the same role as `Rss` — a way to follow, not a social network — which is why it belongs in this catalogue and does not open the door to an icon library.
1733
-
1734
- - No own props: it passes through those of the element or primitive it wraps.
1735
-
1736
- **Website**
1737
- «My other site»: the personal domain in a footer full of social networks.
1738
-
1739
- - No own props: it passes through those of the element or primitive it wraps.
1740
-
1741
1726
  ## Icons
1742
1727
 
1743
1728
  Imported from `@eduardoalvarez/arrecife/icons` · requires `@phosphor-icons/react`. 1 exports.
@@ -1863,6 +1848,7 @@ A series over time, with the fill fading out underneath it.
1863
1848
  | `series` | `readonly ChartSeries[]` | yes | | |
1864
1849
  | `stacked` | `boolean \| undefined` | | `false` | Adds the series up instead of overlaying them. |
1865
1850
  | `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
1851
+ | `valueMax` | `number` | | | The top of the value axis, when the scale has one that the data does not reach — 100 for a percentage. |
1866
1852
  | `xKey` | `string` | yes | | The key on the category axis: the day, the month, the course. |
1867
1853
  | `xTickFormatter` | `(value: unknown) => string` | | | Formats the TICK on the category axis. Returns a string, because an axis tick is an SVG `<text>` and not a place a node can go. |
1868
1854
  | `yTickFormatter` | `(value: unknown) => string` | | | Formats the tick on the VALUE axis — the currency symbol, the thousands separator, the percent sign. |
@@ -1883,6 +1869,7 @@ Bars, upright or lying down.
1883
1869
  | `series` | `readonly ChartSeries[]` | yes | | |
1884
1870
  | `stacked` | `boolean \| undefined` | | `false` | Stacks the series instead of putting them side by side. |
1885
1871
  | `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
1872
+ | `valueMax` | `number` | | | The top of the value axis, when the scale has one that the data does not reach — 100 for a percentage. |
1886
1873
  | `xKey` | `string` | yes | | The key on the category axis: the day, the month, the course. |
1887
1874
  | `xTickFormatter` | `(value: unknown) => string` | | | Formats the TICK on the category axis. Returns a string, because an axis tick is an SVG `<text>` and not a place a node can go. |
1888
1875
  | `yTickFormatter` | `(value: unknown) => string` | | | Formats the tick on the VALUE axis — the currency symbol, the thousands separator, the percent sign. |
@@ -1901,6 +1888,7 @@ Lines, for comparing series against each other.
1901
1888
  | `legend` | `boolean \| undefined` | | | Shows the legend. It defaults to «only when there is more than one series»: a legend naming the one line already named by the chart's own heading is a row of pixels that says nothing. |
1902
1889
  | `series` | `readonly ChartSeries[]` | yes | | |
1903
1890
  | `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
1891
+ | `valueMax` | `number` | | | The top of the value axis, when the scale has one that the data does not reach — 100 for a percentage. |
1904
1892
  | `xKey` | `string` | yes | | The key on the category axis: the day, the month, the course. |
1905
1893
  | `xTickFormatter` | `(value: unknown) => string` | | | Formats the TICK on the category axis. Returns a string, because an axis tick is an SVG `<text>` and not a place a node can go. |
1906
1894
  | `yTickFormatter` | `(value: unknown) => string` | | | Formats the tick on the VALUE axis — the currency symbol, the thousands separator, the percent sign. |
@@ -1911,28 +1899,6 @@ The root re-exports everything from `./tokens` and `./brand` for convenience.
1911
1899
  Each one appears exactly once, under the most specific subpath that publishes
1912
1900
  it: if the code does not mount React, that subpath is the one to import.
1913
1901
 
1914
- ### `@eduardoalvarez/arrecife/social/data`
1915
-
1916
- | export | type | what it is |
1917
- | --- | --- | --- |
1918
- | `discordGlyph` | `SocialGlyph` | Discord's mark. Brand, so it is a solid silhouette. |
1919
- | `emailGlyph` | `SocialGlyph` | The envelope. Functional, so it is a 1.6 stroke. |
1920
- | `gitHubGlyph` | `SocialGlyph` | GitHub's mark. Brand, so it is a solid silhouette. |
1921
- | `instagramGlyph` | `SocialGlyph` | Instagram's mark. Brand, so it is a solid silhouette. |
1922
- | `linkedInGlyph` | `SocialGlyph` | LinkedIn's mark. Brand, so it is a solid silhouette. |
1923
- | `newsletterGlyph` | `SocialGlyph` | |
1924
- | `rssGlyph` | `SocialGlyph` | The feed. Functional, so it is a 1.6 stroke — with one filled dot, because a ring that small reads as a smudge. |
1925
- | `SOCIAL_STROKE_WIDTH` | `1.6` | The stroke width of a functional glyph, from the document. |
1926
- | `SOCIAL_VIEW_BOX` | `"0 0 24 24"` | The grid every glyph is drawn on. |
1927
- | `socialGlyphs` | `{ Discord, Email, GitHub, Instagram, LinkedIn, Newsletter, Rss, Website, X, YouTube }` | The whole catalogue, keyed by the name each glyph is exported under in `./social`. |
1928
- | `socialNames` | `readonly ("Discord" \| "Email" \| "GitHub" \| "Instagram" \| "LinkedIn" \| "Newsletter" \| "Rss" \| "Website" \| "X" \| "YouTube")[]` | The ten names, in the order the catalogue declares them. |
1929
- | `socialSvg` | `(name: "Discord" \| "Email" \| "GitHub" \| "Instagram" \| "LinkedIn" \| "Newsletter" \| "Rss" \| "Website" \| "X" \| "YouTube", extra?: Readonly<Record<string, string \| number>>): string` | One glyph as a complete `<svg>` string, for a template that cannot mount React. |
1930
- | `websiteGlyph` | `SocialGlyph` | «My other site», and the reason it is here rather than borrowed. |
1931
- | `xGlyph` | `SocialGlyph` | X's mark. Brand, so it is a solid silhouette. |
1932
- | `youTubeGlyph` | `SocialGlyph` | YouTube's mark. Brand, so it is a solid silhouette. |
1933
-
1934
- Types (3): `SocialGlyph`, `SocialGlyphShape`, `SocialName`.
1935
-
1936
1902
  ### `@eduardoalvarez/arrecife/variants`
1937
1903
 
1938
1904
  | export | type | what it is |
@@ -1974,10 +1940,6 @@ Types (3): `SocialGlyph`, `SocialGlyphShape`, `SocialName`.
1974
1940
 
1975
1941
  Types (13): `BrandToken`, `ColorMode`, `ColorToken`, `ControlToken`, `FontToken`, `GradientToken`, `RadiusToken`, `SeriesToken`, `SizeToken`, `SpacingToken`, `SyntaxToken`, `Tokens`, `TypeScaleToken`.
1976
1942
 
1977
- ### `@eduardoalvarez/arrecife/social`
1978
-
1979
- Types (1): `SocialIconProps`.
1980
-
1981
1943
  ### `@eduardoalvarez/arrecife/theme`
1982
1944
 
1983
1945
  | export | type | what it is |
@@ -2007,7 +1969,7 @@ Types (2): `Theme`, `ThemeOptions`.
2007
1969
  | `poseList` | `readonly ("desk" \| "laptop-coffee" \| "peek" \| "surf")[]` | |
2008
1970
  | `poses` | `{ readonly desk: "pose-desk.png"; readonly 'laptop-coffee': "pose-laptop-coffee.png"; readonly peek: "pose-peek.png"; readonly surf: "pose-surf.png"; }` | Full-body poses. |
2009
1971
 
2010
- Types (8): `Background`, `Face`, `Fin`, `IsotypeProps`, `LogoProps`, `MascotFaceProps`, `MascotProps`, `Pose`.
1972
+ Types (9): `Background`, `Face`, `Fin`, `IsotypeBackground`, `IsotypeProps`, `LogoProps`, `MascotFaceProps`, `MascotProps`, `Pose`.
2011
1973
 
2012
1974
  ### `@eduardoalvarez/arrecife/icons`
2013
1975
 
@@ -2059,7 +2021,6 @@ Types (6): `ArticleData`, `BaseData`, `CourseData`, `DefaultData`, `SatoriNode`,
2059
2021
  | export | type | what it is |
2060
2022
  | --- | --- | --- |
2061
2023
  | `cn` | `(...inputs: ClassValue[]): string` | |
2062
- | `social` | `typeof import("src/social/index")` | |
2063
2024
  | `toast` | `(message: ReactNode, options?: ToastOptions \| undefined): string` | Fires a notice. It returns its id, which is what you keep in order to close it by hand — the «guardando…» case that gets replaced when the request finishes. |
2064
2025
  | `useTheme` | `(): Theme` | The theme set right now, for a project that needs to branch in React — a different logo per mode, an image with no light version. |
2065
2026
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eduardoalvarez/arrecife",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "The component library of Eduardo Álvarez’s visual identity",
5
5
  "license": "MIT",
6
6
  "author": "Eduardo Esteban Álvarez Castañeda <soy@eduardoalvarez.dev>",
@@ -49,16 +49,6 @@
49
49
  "import": "./dist/brand/index.js",
50
50
  "require": "./dist/brand/index.cjs"
51
51
  },
52
- "./social": {
53
- "types": "./dist/social/index.d.ts",
54
- "import": "./dist/social/index.js",
55
- "require": "./dist/social/index.cjs"
56
- },
57
- "./social/data": {
58
- "types": "./dist/social/data.d.ts",
59
- "import": "./dist/social/data.js",
60
- "require": "./dist/social/data.cjs"
61
- },
62
52
  "./icons": {
63
53
  "types": "./dist/icons/index.d.ts",
64
54
  "import": "./dist/icons/index.js",
@@ -126,9 +116,6 @@
126
116
  "recharts": "^3.0.0"
127
117
  },
128
118
  "peerDependenciesMeta": {
129
- "@phosphor-icons/react": {
130
- "optional": true
131
- },
132
119
  "react-hook-form": {
133
120
  "optional": true
134
121
  },
@@ -139,32 +126,33 @@
139
126
  "devDependencies": {
140
127
  "@eslint/js": "^10.0.1",
141
128
  "@phosphor-icons/react": "^2.1.10",
142
- "@storybook/addon-a11y": "^10.5.10",
143
- "@storybook/addon-docs": "^10.5.10",
144
- "@storybook/addon-themes": "^10.5.10",
145
- "@storybook/addon-vitest": "^10.5.10",
146
- "@storybook/react-vite": "^10.5.10",
129
+ "@storybook/addon-a11y": "^10.6.0",
130
+ "@storybook/addon-docs": "^10.6.0",
131
+ "@storybook/addon-themes": "^10.6.0",
132
+ "@storybook/addon-vitest": "^10.6.0",
133
+ "@storybook/react-vite": "^10.6.0",
147
134
  "@tailwindcss/vite": "^4.3.3",
148
135
  "@types/react": "^19.2.0",
149
- "@types/react-dom": "^19.2.5",
150
- "@vitest/browser": "^4.1.11",
151
- "@vitest/browser-playwright": "^4.1.11",
152
- "eslint": "^10.9.0",
136
+ "@types/react-dom": "^19.2.7",
137
+ "@vitest/browser": "^5.0.0",
138
+ "@vitest/browser-playwright": "^5.0.0",
139
+ "eslint": "^10.10.0",
153
140
  "eslint-plugin-react-hooks": "^7.0.0",
154
- "globals": "^17.11.0",
155
- "playwright": "^1.62.1",
141
+ "globals": "^17.12.0",
142
+ "playwright": "^1.63.0",
156
143
  "react": "^19.2.0",
157
144
  "react-dom": "^19.2.0",
158
145
  "react-hook-form": "^7.87.0",
159
146
  "recharts": "^3.10.1",
160
- "storybook": "^10.5.10",
161
- "storybook-addon-pseudo-states": "^10.5.10",
147
+ "storybook": "^10.6.0",
148
+ "storybook-addon-pseudo-states": "^10.6.0",
162
149
  "tailwindcss": "^4.3.3",
163
150
  "tsup": "^8.5.1",
164
- "typescript": "^5.3.0",
165
- "typescript-eslint": "^8.48.0",
151
+ "typescript": "^6.0.3",
152
+ "typescript-eslint": "^8.69.0",
166
153
  "vite": "^8.2.2",
167
- "vitest": "^4.1.11"
154
+ "vitest": "^5.0.0",
155
+ "eslint-plugin-storybook": "10.6.0"
168
156
  },
169
157
  "packageManager": "pnpm@11.20.0",
170
158
  "engines": {
@@ -1,72 +0,0 @@
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 };