@hackcove/ui-react 0.10.2 → 0.10.4

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 (45) hide show
  1. package/README.md +23 -5
  2. package/dist/AppShell.js +5 -27
  3. package/dist/Avatar.d.ts +1 -1
  4. package/dist/Avatar.js +4 -5
  5. package/dist/Checkbox.d.ts +3 -1
  6. package/dist/Checkbox.js +7 -2
  7. package/dist/CodeBlock.d.ts +2 -2
  8. package/dist/CodeBlock.js +2 -2
  9. package/dist/CodeBody.d.ts +2 -1
  10. package/dist/CodeBody.js +3 -2
  11. package/dist/CodeExample.d.ts +4 -3
  12. package/dist/CodeExample.js +4 -2
  13. package/dist/CopyButton.d.ts +3 -3
  14. package/dist/CopyButton.js +34 -14
  15. package/dist/CopyLine.js +1 -1
  16. package/dist/Dialog.js +1 -1
  17. package/dist/Hero.d.ts +23 -0
  18. package/dist/Hero.js +28 -0
  19. package/dist/Icon.d.ts +2 -1
  20. package/dist/Icon.js +2 -1
  21. package/dist/Image.d.ts +1 -1
  22. package/dist/Image.js +4 -5
  23. package/dist/NavigationProgress.d.ts +20 -0
  24. package/dist/NavigationProgress.js +26 -0
  25. package/dist/PageLayouts.js +5 -3
  26. package/dist/SidebarLayout.d.ts +5 -2
  27. package/dist/SidebarLayout.js +54 -4
  28. package/dist/SiteSearch.d.ts +32 -0
  29. package/dist/SiteSearch.js +150 -0
  30. package/dist/Stack.d.ts +6 -1
  31. package/dist/Stack.js +7 -2
  32. package/dist/copyScript.d.ts +13 -0
  33. package/dist/copyScript.js +13 -0
  34. package/dist/icons.d.ts +2 -2
  35. package/dist/icons.js +30 -0
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.js +4 -0
  38. package/dist/scrollEdges.d.ts +12 -0
  39. package/dist/scrollEdges.js +40 -0
  40. package/dist/stickyColumn.d.ts +18 -0
  41. package/dist/stickyColumn.js +18 -0
  42. package/dist/typography.js +6 -2
  43. package/dist/useImageFailed.d.ts +19 -0
  44. package/dist/useImageFailed.js +21 -0
  45. package/package.json +2 -12
@@ -13,5 +13,23 @@
13
13
  */
14
14
  export declare const STICKY_ASIDE_MD = "md:sticky md:top-[var(--app-header,0px)] md:self-start";
15
15
  export declare const STICKY_SCROLL_MD = "md:-m-1 md:max-h-[calc(100dvh-var(--app-header,0px)-2rem)] md:overflow-y-auto md:p-1";
16
+ /**
17
+ * The thin themed scrollbar (`themedScrollbar` in `cx.ts`) on a rail's scroller from `md`, where it
18
+ * scrolls, instead of the heavy native one with arrow buttons. Its thumb is the scroller's own text
19
+ * colour, so the rail's real ink goes back on an inner box (`STICKY_INK_MD`), the colour the shell
20
+ * gives every page.
21
+ */
22
+ export declare const STICKY_BAR_MD = "md:text-slate-400 md:[scrollbar-width:thin] md:[scrollbar-color:currentColor_transparent]";
23
+ export declare const STICKY_INK_MD = "md:text-slate-900";
16
24
  export declare const STICKY_ASIDE_LG = "lg:sticky lg:top-[var(--app-header,0px)] lg:self-start";
17
25
  export declare const STICKY_SCROLL_LG = "lg:-m-1 lg:max-h-[calc(100dvh-var(--app-header,0px)-2rem)] lg:overflow-y-auto lg:p-1";
26
+ /**
27
+ * While a sticky column's scroller has more to scroll to on a side, that edge fades out (a mask, so
28
+ * it follows any theme), the way AppShell's header row does. `useScrollEdges` sets `data-more-start`
29
+ * / `data-more-end` (and `data-more` for either); a column that fits has none and is untouched.
30
+ * Each is prefixed with its breakpoint, below which the column neither sticks nor scrolls and gets
31
+ * no mask. While the scroller itself holds the keyboard's tab stop (`focus-visible`) the mask is
32
+ * off, since it would hide the focus ring (an outline) at the faded edges.
33
+ */
34
+ export declare const STICKY_FADE_MD = "md:data-more-start:[--fade-start:1.5rem] md:data-more-end:[--fade-end:1.5rem] md:data-more:[mask-image:linear-gradient(to_bottom,transparent,#000_var(--fade-start,0px),#000_calc(100%_-_var(--fade-end,0px)),transparent)] md:data-more:focus-visible:[mask-image:none]";
35
+ export declare const STICKY_FADE_LG = "lg:data-more-start:[--fade-start:1.5rem] lg:data-more-end:[--fade-end:1.5rem] lg:data-more:[mask-image:linear-gradient(to_bottom,transparent,#000_var(--fade-start,0px),#000_calc(100%_-_var(--fade-end,0px)),transparent)] lg:data-more:focus-visible:[mask-image:none]";
@@ -13,5 +13,23 @@
13
13
  */
14
14
  export const STICKY_ASIDE_MD = "md:sticky md:top-[var(--app-header,0px)] md:self-start";
15
15
  export const STICKY_SCROLL_MD = "md:-m-1 md:max-h-[calc(100dvh-var(--app-header,0px)-2rem)] md:overflow-y-auto md:p-1";
16
+ /**
17
+ * The thin themed scrollbar (`themedScrollbar` in `cx.ts`) on a rail's scroller from `md`, where it
18
+ * scrolls, instead of the heavy native one with arrow buttons. Its thumb is the scroller's own text
19
+ * colour, so the rail's real ink goes back on an inner box (`STICKY_INK_MD`), the colour the shell
20
+ * gives every page.
21
+ */
22
+ export const STICKY_BAR_MD = "md:text-slate-400 md:[scrollbar-width:thin] md:[scrollbar-color:currentColor_transparent]";
23
+ export const STICKY_INK_MD = "md:text-slate-900";
16
24
  export const STICKY_ASIDE_LG = "lg:sticky lg:top-[var(--app-header,0px)] lg:self-start";
17
25
  export const STICKY_SCROLL_LG = "lg:-m-1 lg:max-h-[calc(100dvh-var(--app-header,0px)-2rem)] lg:overflow-y-auto lg:p-1";
26
+ /**
27
+ * While a sticky column's scroller has more to scroll to on a side, that edge fades out (a mask, so
28
+ * it follows any theme), the way AppShell's header row does. `useScrollEdges` sets `data-more-start`
29
+ * / `data-more-end` (and `data-more` for either); a column that fits has none and is untouched.
30
+ * Each is prefixed with its breakpoint, below which the column neither sticks nor scrolls and gets
31
+ * no mask. While the scroller itself holds the keyboard's tab stop (`focus-visible`) the mask is
32
+ * off, since it would hide the focus ring (an outline) at the faded edges.
33
+ */
34
+ export const STICKY_FADE_MD = "md:data-more-start:[--fade-start:1.5rem] md:data-more-end:[--fade-end:1.5rem] md:data-more:[mask-image:linear-gradient(to_bottom,transparent,#000_var(--fade-start,0px),#000_calc(100%_-_var(--fade-end,0px)),transparent)] md:data-more:focus-visible:[mask-image:none]";
35
+ export const STICKY_FADE_LG = "lg:data-more-start:[--fade-start:1.5rem] lg:data-more-end:[--fade-end:1.5rem] lg:data-more:[mask-image:linear-gradient(to_bottom,transparent,#000_var(--fade-start,0px),#000_calc(100%_-_var(--fade-end,0px)),transparent)] lg:data-more:focus-visible:[mask-image:none]";
@@ -118,6 +118,8 @@ const SECTION = {
118
118
  app: "text-base/6 font-semibold",
119
119
  prose: "text-2xl/8 font-semibold tracking-tight",
120
120
  };
121
+ /** The offset an anchored heading keeps from the top, so the sticky header does not cover it. */
122
+ const SCROLL_TARGET = "scroll-mt-28";
121
123
  /**
122
124
  * Top-level section header. `h2`, text-base/6 semibold, primary text (text-2xl/8 semibold tight
123
125
  * inside a `Prose`). `rule` sets
@@ -129,10 +131,12 @@ export function SectionHeading({ rule, description, ...props }) {
129
131
  const hairline = "mb-4 border-b border-slate-200/70 pb-2";
130
132
  const size = useProse() ? SECTION.prose : SECTION.app;
131
133
  const hasDescription = description != null && description !== false;
134
+ // An `id` makes the heading a link target, so an anchor jump leaves it clear of the sticky header.
135
+ const scroll = props.id === undefined ? undefined : SCROLL_TARGET;
132
136
  if (!hasDescription) {
133
- return pinned("h2", cx(rule && hairline, size), "default", props);
137
+ return pinned("h2", cx(rule && hairline, size, scroll), "default", props);
134
138
  }
135
- return (_jsxs("div", { className: cx("flex flex-col gap-1", rule && hairline), children: [pinned("h2", size, "default", props), _jsx(Text, { as: "p", color: "tertiary", children: description })] }));
139
+ return (_jsxs("div", { className: cx("flex flex-col gap-1", rule && hairline), children: [pinned("h2", cx(size, scroll), "default", props), _jsx(Text, { as: "p", color: "tertiary", children: description })] }));
136
140
  }
137
141
  const SUBSECTION_SIZE = {
138
142
  sm: { app: "text-sm/5 font-semibold", prose: "text-base/7 font-semibold" },
@@ -0,0 +1,19 @@
1
+ import { type RefObject } from "react";
2
+ /**
3
+ * Whether the picture at `src` has failed to load, for `Avatar` and `Image`, which swap a failed
4
+ * picture for a fallback. A new `src` gets its own try, so a broken one never sticks.
5
+ *
6
+ * A failure is caught two ways. `markFailed` is the img's `onError`, for a picture that fails
7
+ * after React has attached. On a server-rendered page the browser can fail the picture before
8
+ * hydration, when the `error` event is already gone, so once the img mounts it is checked as well:
9
+ * finished (`complete`) with no width (`naturalWidth` 0) means it failed. A picture still loading
10
+ * or already loaded is left alone.
11
+ */
12
+ export declare function useImageFailed(src: string | undefined): {
13
+ /** Pass to the <img>. */
14
+ ref: RefObject<HTMLImageElement | null>;
15
+ /** True when there is no `src`, or this `src` has failed. */
16
+ failed: boolean;
17
+ /** Pass to the <img>'s `onError`. */
18
+ markFailed: () => void;
19
+ };
@@ -0,0 +1,21 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ /**
3
+ * Whether the picture at `src` has failed to load, for `Avatar` and `Image`, which swap a failed
4
+ * picture for a fallback. A new `src` gets its own try, so a broken one never sticks.
5
+ *
6
+ * A failure is caught two ways. `markFailed` is the img's `onError`, for a picture that fails
7
+ * after React has attached. On a server-rendered page the browser can fail the picture before
8
+ * hydration, when the `error` event is already gone, so once the img mounts it is checked as well:
9
+ * finished (`complete`) with no width (`naturalWidth` 0) means it failed. A picture still loading
10
+ * or already loaded is left alone.
11
+ */
12
+ export function useImageFailed(src) {
13
+ const [failedSrc, setFailedSrc] = useState();
14
+ const ref = useRef(null);
15
+ useEffect(() => {
16
+ const img = ref.current;
17
+ if (src && img?.complete && img.naturalWidth === 0)
18
+ setFailedSrc(src);
19
+ }, [src]);
20
+ return { ref, failed: !src || src === failedSrc, markFailed: () => setFailedSrc(src) };
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackcove/ui-react",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Hack Cove React components: primitives, typography roles and a safe Markdown renderer, written against Tailwind v4 utilities. Pair with a theme from @hackcove/themes.",
5
5
  "keywords": [
6
6
  "components",
@@ -78,16 +78,6 @@
78
78
  }
79
79
  },
80
80
  "nx": {
81
- "name": "ui-react",
82
- "tags": [
83
- "kind:package"
84
- ],
85
- "targets": {
86
- "build": {
87
- "outputs": [
88
- "{projectRoot}/dist"
89
- ]
90
- }
91
- }
81
+ "name": "ui-react"
92
82
  }
93
83
  }