@waveso/docs 0.7.1 → 0.9.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/dist/styles.css CHANGED
@@ -1575,6 +1575,71 @@
1575
1575
  font-weight: 600;
1576
1576
  }
1577
1577
 
1578
+ /*
1579
+ * The row is `justify-content: space-between`, so the label has to claim the
1580
+ * space or the icon and the text end up at opposite edges with a hole between
1581
+ * them. `min-width: 0` because a long title in a 240px column must be allowed
1582
+ * to shrink; without it the flex base is the text's min-content width and the
1583
+ * row overflows instead of wrapping.
1584
+ */
1585
+ .wave-docs-sidebar__label,
1586
+ .wave-docs-sidebar__group-title {
1587
+ flex: 1 1 auto;
1588
+ min-width: 0;
1589
+ }
1590
+
1591
+ /*
1592
+ * The type marker: a folder on a group, a page on a page.
1593
+ *
1594
+ * ⚠️ THE BOX IS DECLARED HERE, NOT LEFT TO THE SVG'S OWN WIDTH, because one
1595
+ * of these has no SVG in it. An external link carries its mark at the far end
1596
+ * of the row and needs no second one, so it gets an empty box — and an empty
1597
+ * `<span>` with no size is a column where three labels start 24px left of
1598
+ * every other label. Ragged is the defect this feature exists to remove.
1599
+ */
1600
+ .wave-docs-sidebar__icon {
1601
+ flex: none;
1602
+ inline-size: 1rem;
1603
+ block-size: 1rem;
1604
+ /*
1605
+ * ⚠️ `inherit` AND `opacity`, NOT A COLOUR OF ITS OWN. A fixed grey put a
1606
+ * bold group title next to a marker several steps lighter than it and a
1607
+ * muted page title next to one barely lighter, so the column read as two
1608
+ * different treatments. Fading whatever the row already is keeps one
1609
+ * relationship at every weight — and it is the reason the active row's
1610
+ * marker turns accent-blue with its label rather than staying grey.
1611
+ */
1612
+ color: inherit;
1613
+ opacity: 0.4;
1614
+ }
1615
+
1616
+ /*
1617
+ * A host's own icon, in the column's box.
1618
+ *
1619
+ * `grid` rather than `block`: the component inside is theirs and may be any
1620
+ * size, and a grid with `place-items: center` centres it in our 1rem square
1621
+ * without needing it to fill one. `> *` caps it at the box so an icon
1622
+ * authored at 24 does not push the row taller than every other row.
1623
+ */
1624
+ span.wave-docs-sidebar__icon {
1625
+ display: grid;
1626
+ place-items: center;
1627
+ }
1628
+
1629
+ .wave-docs-sidebar__icon > * {
1630
+ max-inline-size: 100%;
1631
+ max-block-size: 100%;
1632
+ }
1633
+
1634
+ /* Full strength the moment the row is the reader's own or under their
1635
+ * pointer. A current page whose marker stayed faded reads as
1636
+ * half-highlighted. */
1637
+ .wave-docs-sidebar__link:hover .wave-docs-sidebar__icon,
1638
+ .wave-docs-sidebar__group-button:hover .wave-docs-sidebar__icon,
1639
+ .wave-docs-sidebar__link[aria-current='page'] .wave-docs-sidebar__icon {
1640
+ opacity: 1;
1641
+ }
1642
+
1578
1643
  .wave-docs-sidebar__list:not([data-depth='0']) {
1579
1644
  margin-inline-start: 0.5rem;
1580
1645
  padding-inline-start: 0.5rem;
@@ -1646,11 +1711,55 @@
1646
1711
  color: var(--wave-docs-fg);
1647
1712
  }
1648
1713
 
1714
+ /*
1715
+ * The chevron points at the label it belongs to, not away from it.
1716
+ *
1717
+ * `.wave-docs-sidebar__group-header` is `justify-content: space-between`, so
1718
+ * this sits flush against the navigation's inline end with the label at the
1719
+ * other side of the row. The icon is Lucide's `chevron-right`, and unrotated
1720
+ * it therefore aimed at the panel's border — at nothing. Worse than nothing,
1721
+ * in fact: a chevron at the *trailing* edge of a row is the platform idiom
1722
+ * for "this takes you somewhere else", so it read as navigation on a control
1723
+ * that only opens a list in place.
1724
+ *
1725
+ * ⚠️ AND THAT IS WHY THERE IS A MIRROR RULE. `rotate` is physical — `180deg`
1726
+ * is left in every writing mode — while everything else positioning this row
1727
+ * is logical, so the header mirrors under `dir="rtl"` and a lone physical
1728
+ * rotation does not. Mirrored, the chevron moves to the inline start with the
1729
+ * label to its right, and 180deg would then point it out of the panel on the
1730
+ * other side. `0deg` is what "at the label" means there.
1731
+ *
1732
+ * ⚠️ `[dir='rtl']`, AND NOT `:dir(rtl)`, WHICH IS THE SELECTOR FOR THIS JOB
1733
+ * AND DOES NOT SURVIVE THE BUILD. Next compiles this sheet with lightningcss,
1734
+ * which downlevels `:dir(rtl)` into a hardcoded list of right-to-left
1735
+ * *languages*:
1736
+ *
1737
+ * .wave-docs-sidebar__chevron:is(:lang(ae), :lang(ar), … :lang(yi))
1738
+ *
1739
+ * Direction is not language. Measured on the site with `<html dir="rtl"
1740
+ * lang="en">`: `:dir(rtl)` matched the element, the rule was nowhere in the
1741
+ * served stylesheet, and the chevron kept pointing out of the panel. It fails
1742
+ * the other way too — `lang="ar" dir="ltr"` gets the mirror it did not ask
1743
+ * for. The attribute selector is plain CSS 2.1, so no pipeline has an opinion
1744
+ * about it, and it is what `rtl:` compiles to in every design system that
1745
+ * ships one.
1746
+ */
1649
1747
  .wave-docs-sidebar__chevron {
1650
1748
  flex: none;
1651
1749
  color: var(--wave-docs-fg-subtle);
1750
+ rotate: 180deg;
1652
1751
  }
1653
1752
 
1753
+ [dir='rtl'] .wave-docs-sidebar__chevron {
1754
+ rotate: 0deg;
1755
+ }
1756
+
1757
+ /*
1758
+ * Open is down in both directions, so this needs no mirror — and it must
1759
+ * come last, because the mirror above matches with the same specificity
1760
+ * (0,2,0 either way) and would otherwise hold a mirrored open group pointing
1761
+ * sideways.
1762
+ */
1654
1763
  .wave-docs-sidebar__chevron[data-open] {
1655
1764
  rotate: 90deg;
1656
1765
  }
@@ -1673,6 +1782,35 @@
1673
1782
  .wave-docs-sidebar__chevron {
1674
1783
  transition: rotate 150ms ease-out;
1675
1784
  }
1785
+
1786
+ /*
1787
+ * The row and its marker ease together, and the row is half of it.
1788
+ *
1789
+ * ⚠️ THE LINKS HAD NO TRANSITION AT ALL — the TOC's did, which is why this
1790
+ * looked like an oversight only in the sidebar. Fading the marker alone
1791
+ * would have been worse than fading nothing: the glyph would drift up to
1792
+ * full strength while the surface under it snapped, so the two halves of
1793
+ * one hover would visibly disagree.
1794
+ *
1795
+ * `background-color`, not `background`: the shorthand includes
1796
+ * `background-image`, and naming it here would put this rule in the way of
1797
+ * any consumer who paints one.
1798
+ */
1799
+ .wave-docs-sidebar__link,
1800
+ .wave-docs-sidebar__group-button,
1801
+ .wave-docs-sidebar__group-toggle {
1802
+ transition:
1803
+ background-color 150ms ease-out,
1804
+ color 150ms ease-out;
1805
+ }
1806
+
1807
+ /* `color` as well as `opacity`: the marker inherits its row's colour, and
1808
+ * on the current page that is a jump from muted grey to accent blue. */
1809
+ .wave-docs-sidebar__icon {
1810
+ transition:
1811
+ opacity 150ms ease-out,
1812
+ color 150ms ease-out;
1813
+ }
1676
1814
  }
1677
1815
  }
1678
1816
 
@@ -2685,14 +2823,30 @@
2685
2823
  * ------------------------------------------------------------------------ */
2686
2824
 
2687
2825
  @layer components {
2826
+ /*
2827
+ * ⚠️ THE INLINE PADDING AND THE GAP ARE THE NAVIGATION'S, NOT THIS BUTTON'S,
2828
+ * AND THE `- 1px` IS THE WHOLE REASON THEY LOOK THE SAME.
2829
+ *
2830
+ * The trigger sits directly above the tree, so its magnifier is the first
2831
+ * thing in the same column as every folder and page marker below it, and its
2832
+ * label starts the same column as every title. Both were off — measured at
2833
+ * 1280px, the glyph by 3px and the label by 11px — because this button was
2834
+ * spaced as a standalone control: 10px of padding against the rows' 8px, and
2835
+ * a 16px gap against their 8px.
2836
+ *
2837
+ * The rows carry no border and this does, so matching `0.5rem` outright would
2838
+ * leave the glyph 1px out. `calc(0.5rem - 1px)` puts the *content* edge where
2839
+ * theirs is, which is the edge a reader sees. Block padding is untouched: it
2840
+ * sets this control's height, and nothing below it is the same height.
2841
+ */
2688
2842
  .wave-docs-search-trigger {
2689
2843
  display: flex;
2690
2844
  align-items: center;
2691
2845
  justify-content: space-between;
2692
- gap: 1rem;
2846
+ gap: 0.5rem;
2693
2847
  width: 100%;
2694
2848
  max-width: 20rem;
2695
- padding: 0.4375rem 0.625rem;
2849
+ padding: 0.4375rem calc(0.5rem - 1px);
2696
2850
  border: 1px solid var(--wave-docs-border);
2697
2851
  border-radius: var(--wave-docs-radius);
2698
2852
  background: var(--wave-docs-bg-subtle);
@@ -2733,14 +2887,26 @@
2733
2887
  outline-offset: 2px;
2734
2888
  }
2735
2889
 
2736
- /* The label is the flexible half of the trigger; the shortcut hint beside it
2737
- * must never be the thing that gets squeezed out at narrow widths. */
2890
+ /* The label is the flexible half of the trigger; neither the magnifier before
2891
+ * it nor the shortcut hint after it may be the thing squeezed out at narrow
2892
+ * widths. `flex: 1 1 auto` because the row is `space-between` and three
2893
+ * items would otherwise centre the middle one, leaving a gap on both sides of
2894
+ * a short label. */
2738
2895
  .wave-docs-search-trigger-label {
2896
+ flex: 1 1 auto;
2897
+ min-width: 0;
2739
2898
  overflow: hidden;
2740
2899
  text-overflow: ellipsis;
2741
2900
  white-space: nowrap;
2742
2901
  }
2743
2902
 
2903
+ /* Quieter than the words either side of it, in the trigger and in the dialog
2904
+ * alike — it is a category marker, not a control. */
2905
+ .wave-docs-search-glyph {
2906
+ flex: none;
2907
+ color: var(--wave-docs-fg-subtle);
2908
+ }
2909
+
2744
2910
  /*
2745
2911
  * ⚠️ HIDDEN WHERE THE SHORTCUT CANNOT BE PRESSED, WHICH IS NOT A WIDTH
2746
2912
  * QUESTION. `⌘K` is an instruction, and on a phone or a tablet it is one the
@@ -2753,16 +2919,61 @@
2753
2919
  }
2754
2920
  }
2755
2921
 
2756
- .wave-docs-search-trigger-kbd {
2922
+ .wave-docs-search-trigger-kbd,
2923
+ .wave-docs-search-kbd {
2757
2924
  flex: none;
2758
- padding: 0.05rem 0.35rem;
2759
- border: 1px solid var(--wave-docs-border-strong);
2760
- border-radius: var(--wave-docs-radius-sm);
2761
2925
  color: var(--wave-docs-fg-subtle);
2762
2926
  font-family: var(--wave-docs-font-mono);
2763
2927
  font-size: 0.75rem;
2764
2928
  }
2765
2929
 
2930
+ /*
2931
+ * The footer's caps are drawn as keys; the trigger's shortcut is not.
2932
+ *
2933
+ * They were one rule, and the trigger's read as a chip on a surface that is
2934
+ * already a chip — a bordered box inside a bordered box, sharing its fill,
2935
+ * for a hint nobody clicks. The footer's caps sit on the flat bottom of the
2936
+ * dialog and are the only thing there that has to look pressable.
2937
+ *
2938
+ * No `background` was ever set on either: the trigger's own
2939
+ * `--wave-docs-bg-subtle` showed through, which is what made the border read
2940
+ * as a filled shape. Removing the border is what removes the shape.
2941
+ */
2942
+ .wave-docs-search-kbd {
2943
+ padding: 0.05rem 0.35rem;
2944
+ border: 1px solid var(--wave-docs-border-strong);
2945
+ border-radius: var(--wave-docs-radius-sm);
2946
+ }
2947
+
2948
+ /*
2949
+ * ⚠️ THE SYMBOL IS SCALED AND THE WORD IS NOT — which is why this is a
2950
+ * separate element rather than one string in the `<kbd>`.
2951
+ *
2952
+ * Measured in the shipped mono stack at 12px: `⌘` has 6.39px of ink against
2953
+ * `K`'s 8.75px, so at a shared size it sits a third short of the letter next
2954
+ * to it. `1.35em` is that ratio, and it makes the two read as one mark.
2955
+ *
2956
+ * `Ctrl` gets none of it: it is a word set in the same face as the `K`, and
2957
+ * scaling it would make the hint shout.
2958
+ */
2959
+ .wave-docs-search-trigger-mod[data-symbol] {
2960
+ font-size: 1.45em;
2961
+ line-height: 1;
2962
+ /*
2963
+ * ⚠️ AND IT HAS TO BE MOVED DOWN, WHICH `font-size` ALONE WILL NEVER DO.
2964
+ *
2965
+ * `⌘` is drawn around the font's mathematical axis, not standing on the
2966
+ * baseline like a capital. Measured at this size: its ink runs from 11.24px
2967
+ * above the baseline to 1.98px *above* it — a centre 6.61px up — while the
2968
+ * `K` beside it runs 8.75px to 0, centred 4.38px up. Aligned by baseline,
2969
+ * as inline text is, the symbol therefore floats 2.24px high.
2970
+ *
2971
+ * `-0.13em` of this element's own 17.4px is 2.26px, which puts the two ink
2972
+ * centres within a rounding error of each other.
2973
+ */
2974
+ vertical-align: -0.13em;
2975
+ }
2976
+
2766
2977
  .wave-docs-search-backdrop {
2767
2978
  position: fixed;
2768
2979
  inset: 0;
@@ -2851,21 +3062,94 @@
2851
3062
  color: var(--wave-docs-fg-subtle);
2852
3063
  }
2853
3064
 
2854
- .wave-docs-search-close {
3065
+ /*
3066
+ * The keyboard footer.
3067
+ *
3068
+ * ⚠️ `flex: none`, BECAUSE THE LIST ABOVE IT IS `flex: 1 1 auto`. The dialog
3069
+ * is a column with a ceiling, and the results are what takes the slack and
3070
+ * scrolls; without this the footer is a second growable item and the two
3071
+ * share the overflow — a footer that drifts down the dialog as results
3072
+ * arrive, and a list that never quite reaches it.
3073
+ */
3074
+ .wave-docs-search-footer {
2855
3075
  flex: none;
2856
- padding: 0.3125rem 0.625rem;
2857
- border: 1px solid var(--wave-docs-border);
3076
+ display: flex;
3077
+ align-items: center;
3078
+ gap: 1rem;
3079
+ padding: 0.5rem 0.75rem;
3080
+ border-block-start: 1px solid var(--wave-docs-border);
3081
+ color: var(--wave-docs-fg-subtle);
3082
+ font-size: 0.75rem;
3083
+ }
3084
+
3085
+ .wave-docs-search-hint,
3086
+ .wave-docs-search-close {
3087
+ display: inline-flex;
3088
+ align-items: center;
3089
+ gap: 0.375rem;
3090
+ }
3091
+
3092
+ /*
3093
+ * ⚠️ A NEGATIVE MARGIN, BECAUSE `gap` CANNOT VARY PER PAIR. `↑` and `↓` are
3094
+ * one instruction and read as one object; the gap that separates a cap from
3095
+ * the word beside it is too wide between two caps that belong together. A
3096
+ * flex container has a single gap, so the only ways to tighten one pair are
3097
+ * an extra wrapper element or this — and a wrapper on every hint is markup
3098
+ * bought for spacing.
3099
+ *
3100
+ * `-0.25rem` against the `0.375rem` above leaves `0.125rem` between them.
3101
+ */
3102
+ .wave-docs-search-kbd + .wave-docs-search-kbd {
3103
+ margin-inline-start: -0.25rem;
3104
+ }
3105
+
3106
+ /*
3107
+ * The only cap holding a word rather than a glyph, and three letters at the
3108
+ * arrows' size made it the loudest thing in the footer. `line-height` pins
3109
+ * the box so it still sits at the same height as the caps beside it —
3110
+ * without it the smaller text shrinks the content box and the cap rides
3111
+ * high in a row of taller ones.
3112
+ */
3113
+ .wave-docs-search-close .wave-docs-search-kbd {
3114
+ font-size: 0.625rem;
3115
+ /* Solved, not guessed: the cap is 3.6px of border and padding plus its own
3116
+ * line box, and the 12px caps beside it measure 17.6px. 10px x 1.4 = 14,
3117
+ * and 14 + 3.6 is 17.6 — all four exactly the same height. */
3118
+ line-height: 1.4;
3119
+ }
3120
+
3121
+ /*
3122
+ * ⚠️ HIDDEN WHERE THE KEY CANNOT BE PRESSED — the same query, and the same
3123
+ * reason, as the trigger's `⌘K`. An instruction to press an arrow key is one
3124
+ * a reader on a phone cannot follow, and a 10-inch tablet at 1024px would
3125
+ * keep it under any width rule.
3126
+ *
3127
+ * The Close button beside them is deliberately NOT in here. It is the only
3128
+ * pointer dismiss affordance in the dialog, and on exactly the devices this
3129
+ * query names there is no Esc to fall back to.
3130
+ */
3131
+ @media (hover: none) and (pointer: coarse) {
3132
+ .wave-docs-search-hint {
3133
+ display: none;
3134
+ }
3135
+ }
3136
+
3137
+ /* Pushed to the far end, away from the two hints — the difference between a
3138
+ * control and a caption, said with position. */
3139
+ .wave-docs-search-close {
3140
+ margin-inline-start: auto;
3141
+ padding: 0.125rem 0.25rem;
3142
+ border: 0;
2858
3143
  border-radius: var(--wave-docs-radius-sm);
2859
3144
  background: none;
2860
3145
  color: var(--wave-docs-fg-muted);
2861
3146
  font: inherit;
2862
- font-size: 0.8125rem;
3147
+ font-size: 0.75rem;
2863
3148
  cursor: pointer;
2864
3149
  }
2865
3150
 
2866
3151
  .wave-docs-search-close:hover {
2867
3152
  color: var(--wave-docs-fg);
2868
- border-color: var(--wave-docs-border-strong);
2869
3153
  }
2870
3154
 
2871
3155
  /*
package/dist/types.d.ts CHANGED
@@ -59,6 +59,20 @@ interface DocFrontmatter {
59
59
  * Lower sorts first; pages without an order sort last, alphabetically.
60
60
  */
61
61
  order?: number | undefined;
62
+ /**
63
+ * Sidebar marker for this page, as a name the *consumer* resolves.
64
+ *
65
+ * A name and not a component, because frontmatter is data: it is authored by
66
+ * whoever writes the page, in YAML, and cannot carry a React element. The
67
+ * host maps the name to their own icon via `DocsSidebar`'s `icons` prop, so
68
+ * the art belongs to whichever site is rendering — which is the only shape
69
+ * that serves a package mounted inside someone else's application.
70
+ *
71
+ * An unmapped name falls back to the default page marker rather than
72
+ * rendering nothing: a typo in one file should not knock a hole in the
73
+ * column.
74
+ */
75
+ icon?: string | undefined;
62
76
  /**
63
77
  * Calls to action, and the opt-in for the page's hero.
64
78
  *
@@ -122,6 +136,8 @@ interface DocNavPage {
122
136
  title: string;
123
137
  href: string;
124
138
  slug: string;
139
+ /** Marker name from the page's frontmatter. See {@link DocFrontmatter.icon}. */
140
+ icon?: string | undefined;
125
141
  }
126
142
  /**
127
143
  * A directory. `href` is present when the directory has an `index.md`, in
@@ -132,6 +148,8 @@ interface DocNavGroup {
132
148
  title: string;
133
149
  href?: string | undefined;
134
150
  children: DocNavNode[];
151
+ /** Marker name from the directory's `meta.json`. */
152
+ icon?: string | undefined;
135
153
  }
136
154
  /** A non-interactive heading between groups, from `"---Label---"` in meta.json. */
137
155
  interface DocNavSeparator {
@@ -144,6 +162,8 @@ interface DocNavLink {
144
162
  title: string;
145
163
  href: string;
146
164
  external: boolean;
165
+ /** Marker name from the `meta.json` entry that declared this link. */
166
+ icon?: string | undefined;
147
167
  }
148
168
  type DocNavNode = DocNavPage | DocNavGroup | DocNavSeparator | DocNavLink;
149
169
  /**
@@ -164,10 +184,17 @@ type DocNavNode = DocNavPage | DocNavGroup | DocNavSeparator | DocNavLink;
164
184
  interface DocsMeta {
165
185
  /** Directory title, shown as the group heading. Defaults to the dirname. */
166
186
  title?: string | undefined;
187
+ /**
188
+ * Sidebar marker for this directory, as a name the consumer resolves. See
189
+ * {@link DocFrontmatter.icon} — the same contract, declared where the
190
+ * directory is described rather than where a page is.
191
+ */
192
+ icon?: string | undefined;
167
193
  /** Ordered entries. Omit to sort by frontmatter `order`, then alphabetically. */
168
194
  pages?: Array<string | {
169
195
  title: string;
170
196
  href: string;
197
+ icon?: string | undefined;
171
198
  }> | undefined;
172
199
  }
173
200
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveso/docs",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "description": "Zero parser bytes in the browser: markdown docs for Next.js, built to hast in Node and rendered as your components",
5
5
  "type": "module",
6
6
  "sideEffects": [