@haruhimemoe/ui 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 (70) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/README.md +143 -33
  3. package/dist/components/basics/Disclosure.d.ts +1 -1
  4. package/dist/components/basics/Disclosure.js +2 -2
  5. package/dist/components/filters/RangeSlider.d.ts +1 -1
  6. package/dist/components/filters/RangeSlider.js +6 -6
  7. package/dist/components/filters/chipStyles.d.ts +2 -2
  8. package/dist/components/filters/chipStyles.js +3 -2
  9. package/dist/components/forms/CharCounter.d.ts +4 -2
  10. package/dist/components/forms/CharCounter.js +5 -3
  11. package/dist/components/forms/Checkbox.d.ts +1 -1
  12. package/dist/components/forms/Checkbox.js +3 -1
  13. package/dist/components/forms/FieldFrame.d.ts +1 -1
  14. package/dist/components/forms/FieldFrame.js +1 -1
  15. package/dist/components/forms/RadioGroup.d.ts +1 -1
  16. package/dist/components/forms/RadioGroup.js +4 -2
  17. package/dist/components/forms/ReportDisclosure.d.ts +1 -1
  18. package/dist/components/forms/ReportDisclosure.js +10 -6
  19. package/dist/components/forms/fieldStyles.d.ts +5 -6
  20. package/dist/components/forms/fieldStyles.js +6 -7
  21. package/dist/components/osu/BeatmapStats.d.ts +1 -1
  22. package/dist/components/osu/BeatmapStats.js +2 -1
  23. package/dist/components/osu/starColors.d.ts +14 -4
  24. package/dist/components/osu/starColors.js +39 -2
  25. package/dist/components/palette/CommandPalette.d.ts +21 -0
  26. package/dist/components/palette/CommandPalette.js +315 -0
  27. package/dist/components/palette/CommandPaletteButton.d.ts +21 -0
  28. package/dist/components/palette/CommandPaletteButton.js +26 -0
  29. package/dist/components/palette/PaletteFooter.d.ts +21 -0
  30. package/dist/components/palette/PaletteFooter.js +10 -0
  31. package/dist/components/palette/PaletteInput.d.ts +30 -0
  32. package/dist/components/palette/PaletteInput.js +23 -0
  33. package/dist/components/palette/PaletteList.d.ts +29 -0
  34. package/dist/components/palette/PaletteList.js +44 -0
  35. package/dist/components/palette/PaletteRow.d.ts +30 -0
  36. package/dist/components/palette/PaletteRow.js +33 -0
  37. package/dist/components/palette/calc.d.ts +28 -0
  38. package/dist/components/palette/calc.js +226 -0
  39. package/dist/components/palette/fuzzy.d.ts +51 -0
  40. package/dist/components/palette/fuzzy.js +138 -0
  41. package/dist/components/palette/hotkeys.d.ts +57 -0
  42. package/dist/components/palette/hotkeys.js +111 -0
  43. package/dist/components/palette/paletteEvents.d.ts +22 -0
  44. package/dist/components/palette/paletteEvents.js +22 -0
  45. package/dist/components/palette/platform.d.ts +13 -0
  46. package/dist/components/palette/platform.js +21 -0
  47. package/dist/components/palette/recents.d.ts +48 -0
  48. package/dist/components/palette/recents.js +85 -0
  49. package/dist/components/palette/rows.d.ts +63 -0
  50. package/dist/components/palette/rows.js +136 -0
  51. package/dist/components/palette/siteCommands.d.ts +38 -0
  52. package/dist/components/palette/siteCommands.js +152 -0
  53. package/dist/components/palette/store.d.ts +94 -0
  54. package/dist/components/palette/store.js +135 -0
  55. package/dist/components/palette/types.d.ts +102 -0
  56. package/dist/components/palette/types.js +11 -0
  57. package/dist/components/palette/useProviderSearch.d.ts +28 -0
  58. package/dist/components/palette/useProviderSearch.js +68 -0
  59. package/dist/components/shell/HeaderMenu.d.ts +1 -1
  60. package/dist/components/shell/HeaderMenu.js +2 -2
  61. package/dist/components/shell/NavItem.d.ts +1 -1
  62. package/dist/components/shell/NavItem.js +2 -2
  63. package/dist/components/shell/SiteFooter.d.ts +9 -4
  64. package/dist/components/shell/SiteFooter.js +4 -2
  65. package/dist/components/tables/Table.d.ts +5 -3
  66. package/dist/components/tables/Table.js +15 -2
  67. package/dist/index.d.ts +8 -1
  68. package/dist/index.js +8 -1
  69. package/dist/theme.css +4 -4
  70. package/package.json +8 -3
@@ -1,18 +1,19 @@
1
1
  /**
2
2
  * @file src/components/shell/SiteFooter.tsx
3
- * @desc Site footer: labelled link columns from data (entries without an href show as text with
3
+ * @desc Site footer: link columns from data under one nav landmark, each a section headed by its title (entries without an href show as text with
4
4
  * a small note, like "soon"), an optional "haruhime tools" column linking the other tools,
5
5
  * an optional extra slot, one line of fine print, the
6
6
  * haruhime.moe wordmark linking the parent site, a GitHub icon link, and an optional
7
7
  * Discord icon link beside it (white, as Discord's brand guidelines ask).
8
8
  * @author David @dvhsh (https://dvh.sh)
9
9
  * @created Wed Sep 23, 2026
10
- * @modified Mon Sep 28, 2026
10
+ * @modified Sat Oct 3, 2026
11
11
  */
12
12
  import type { ComponentProps, ReactNode } from "react";
13
+ import type { HeadingLevel } from "../basics/cardStyles.js";
13
14
  import { type HaruhimeToolsOptions } from "./haruhimeTools.js";
14
15
  import { type SiteLinkItem } from "./links.js";
15
- /** One footer column: a title (also the nav landmark's name) and its entries. */
16
+ /** One footer column: a title (its heading, which names the column's section) and its entries. */
16
17
  export type SiteFooterColumn = {
17
18
  title: string;
18
19
  items: readonly SiteLinkItem[];
@@ -21,6 +22,10 @@ export type SiteFooterColumn = {
21
22
  export type SiteFooterProps = Omit<ComponentProps<"footer">, "children"> & {
22
23
  /** Link columns, left to right. Internal hrefs use `next/link`, external ones a plain `<a>`. */
23
24
  columns?: readonly SiteFooterColumn[] | undefined;
25
+ /** The one nav landmark around the columns. Default "Footer". Keep it unlike the header's. */
26
+ navLabel?: string | undefined;
27
+ /** The column titles' heading level. Default 2. */
28
+ headingLevel?: HeadingLevel | undefined;
24
29
  /**
25
30
  * Adds the "haruhime tools" column (haruhimeToolsColumn): the other haruhime.moe tools and
26
31
  * "All tools". `current` leaves this tool out; `position` places it (default 1).
@@ -49,4 +54,4 @@ export type SiteFooterProps = Omit<ComponentProps<"footer">, "children"> & {
49
54
  * links and their names, and native footer props
50
55
  * @returns {JSX.Element} the footer: columns on top, then extra, fine print and the brand row
51
56
  */
52
- export declare function SiteFooter({ columns: ownColumns, tools, extra, finePrint, parentLink, parentHref, githubHref, githubLabel, discordHref, discordLabel, className, ...props }: SiteFooterProps): import("react").JSX.Element;
57
+ export declare function SiteFooter({ columns: ownColumns, navLabel, headingLevel, tools, extra, finePrint, parentLink, parentHref, githubHref, githubLabel, discordHref, discordLabel, className, ...props }: SiteFooterProps): import("react").JSX.Element;
@@ -20,7 +20,7 @@ const GRID_COLUMNS = ["", "", "sm:grid-cols-2", "sm:grid-cols-3", "sm:grid-cols-
20
20
  * links and their names, and native footer props
21
21
  * @returns {JSX.Element} the footer: columns on top, then extra, fine print and the brand row
22
22
  */
23
- export function SiteFooter({ columns: ownColumns = [], tools, extra, finePrint, parentLink = true, parentHref = "https://www.haruhime.moe", githubHref = "https://github.com/haruhimemoe", githubLabel = "haruhimemoe on GitHub", discordHref, discordLabel = "Discord", className, ...props }) {
23
+ export function SiteFooter({ columns: ownColumns = [], navLabel = "Footer", headingLevel = 2, tools, extra, finePrint, parentLink = true, parentHref = "https://www.haruhime.moe", githubHref = "https://github.com/haruhimemoe", githubLabel = "haruhimemoe on GitHub", discordHref, discordLabel = "Discord", className, ...props }) {
24
24
  const columns = [...ownColumns];
25
25
  if (tools) {
26
26
  const at = Math.max(0, Math.min(tools.position ?? 1, columns.length));
@@ -34,5 +34,7 @@ export function SiteFooter({ columns: ownColumns = [], tools, extra, finePrint,
34
34
  // With the wordmark, the fine print gets its own line and the row holds wordmark + icon.
35
35
  // Without it, the fine print shares the row with the icon.
36
36
  const rowStart = parentLink ? _jsx(HaruhimeWordmarkLink, { href: parentHref }) : fine;
37
- return (_jsx("footer", { className: cx("border-b4 border-t bg-b6 text-c3 text-sm", className), ...props, children: _jsxs("div", { className: "mx-auto flex max-w-5xl flex-col gap-8 px-4 py-10", children: [columns.length > 0 ? (_jsx("div", { className: cx("grid gap-8", GRID_COLUMNS[Math.min(columns.length, 4)]), children: columns.map((column) => (_jsxs("nav", { "aria-label": column.title, children: [_jsx("p", { className: "mb-3 font-bold text-c4 text-xs uppercase tracking-wide", children: column.title }), _jsx("ul", { className: "flex flex-col gap-2", children: column.items.map((item) => (_jsxs("li", { children: [item.href ? (_jsx(AutoLink, { href: item.href, className: "wrap-anywhere transition-colors hover:text-c1", children: item.label })) : (_jsx("span", { children: item.label })), _jsx(LinkNote, { note: item.note })] }, linkItemKey(item)))) })] }, column.title))) })) : null, extra || fine || rowStart || icons ? (_jsxs("div", { className: "flex flex-col gap-3 border-b4 border-t pt-6", children: [extra, parentLink ? fine : null, rowStart || icons ? (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [rowStart, icons] })) : null] })) : null] }) }));
37
+ const Heading = `h${headingLevel}`;
38
+ const columnId = (title) => `footer-${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
39
+ return (_jsx("footer", { className: cx("border-b4 border-t bg-b6 text-c3 text-sm", className), ...props, children: _jsxs("div", { className: "mx-auto flex max-w-5xl flex-col gap-8 px-4 py-10", children: [columns.length > 0 ? (_jsx("nav", { "aria-label": navLabel, className: cx("grid gap-8", GRID_COLUMNS[Math.min(columns.length, 4)]), children: columns.map((column) => (_jsxs("section", { "aria-labelledby": columnId(column.title), children: [_jsx(Heading, { id: columnId(column.title), className: "mb-3 font-bold text-c4 text-xs uppercase tracking-wide", children: column.title }), _jsx("ul", { className: "flex flex-col gap-2", children: column.items.map((item) => (_jsxs("li", { children: [item.href ? (_jsx(AutoLink, { href: item.href, className: "wrap-anywhere transition-colors hover:text-c1", children: item.label })) : (_jsx("span", { children: item.label })), _jsx(LinkNote, { note: item.note })] }, linkItemKey(item)))) })] }, column.title))) })) : null, extra || fine || rowStart || icons ? (_jsxs("div", { className: "flex flex-col gap-3 border-b4 border-t pt-6", children: [extra, parentLink ? fine : null, rowStart || icons ? (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [rowStart, icons] })) : null] })) : null] }) }));
38
40
  }
@@ -5,9 +5,9 @@
5
5
  * Server-safe. Fill it with THead, TBody, Th and Td.
6
6
  * @author David @dvhsh (https://dvh.sh)
7
7
  * @created Mon Sep 28, 2026
8
- * @modified Mon Sep 28, 2026
8
+ * @modified Sat Oct 3, 2026
9
9
  */
10
- import type { ComponentProps, ReactNode } from "react";
10
+ import { type ComponentProps, type ReactNode } from "react";
11
11
  /** Every native `<table>` prop (including `ref`), plus a caption and the wrapper's classes. */
12
12
  export type TableProps = ComponentProps<"table"> & {
13
13
  /** Names the table for screen readers. Shown above it unless `hideCaption`. */
@@ -16,6 +16,8 @@ export type TableProps = ComponentProps<"table"> & {
16
16
  hideCaption?: boolean | undefined;
17
17
  /** Classes for the wrapper `<div>` that scrolls sideways. */
18
18
  wrapperClassName?: string | undefined;
19
+ /** The scroll wrapper's accessible name when there is no caption. Default "Table". */
20
+ scrollLabel?: string | undefined;
19
21
  };
20
22
  /**
21
23
  * @function Table
@@ -23,4 +25,4 @@ export type TableProps = ComponentProps<"table"> & {
23
25
  * wrapperClassName; `className` and `ref` go on the `<table>`
24
26
  * @returns {JSX.Element} an `overflow-x-auto` `<div>` around the `<table>` and its caption
25
27
  */
26
- export declare function Table({ caption, hideCaption, wrapperClassName, className, children, ...props }: TableProps): import("react").JSX.Element;
28
+ export declare function Table({ caption, hideCaption, wrapperClassName, scrollLabel, className, children, ...props }: TableProps): import("react").JSX.Element;
@@ -1,4 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * @file src/components/tables/Table.tsx
4
+ * @desc A data table on the apps' look: full width, small left-aligned text, in a wrapper that
5
+ * scrolls sideways on phones, with an optional caption (visible, or for screen readers only).
6
+ * Server-safe. Fill it with THead, TBody, Th and Td.
7
+ * @author David @dvhsh (https://dvh.sh)
8
+ * @created Mon Sep 28, 2026
9
+ * @modified Sat Oct 3, 2026
10
+ */
11
+ import { useId } from "react";
2
12
  import { cx } from "../../utils/cx.js";
3
13
  import { FIELD_LABEL } from "../forms/fieldStyles.js";
4
14
  /**
@@ -7,6 +17,9 @@ import { FIELD_LABEL } from "../forms/fieldStyles.js";
7
17
  * wrapperClassName; `className` and `ref` go on the `<table>`
8
18
  * @returns {JSX.Element} an `overflow-x-auto` `<div>` around the `<table>` and its caption
9
19
  */
10
- export function Table({ caption, hideCaption = false, wrapperClassName, className, children, ...props }) {
11
- return (_jsx("div", { className: cx("overflow-x-auto", wrapperClassName), children: _jsxs("table", { className: cx("w-full text-left text-sm", className), ...props, children: [caption ? (_jsx("caption", { className: hideCaption ? "sr-only" : `mb-2 text-left ${FIELD_LABEL}`, children: caption })) : null, children] }) }));
20
+ export function Table({ caption, hideCaption = false, wrapperClassName, scrollLabel = "Table", className, children, ...props }) {
21
+ const captionId = useId();
22
+ return (_jsx("section", {
23
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: a scrollable region needs keyboard focus
24
+ tabIndex: 0, "aria-labelledby": caption ? captionId : undefined, "aria-label": caption ? undefined : scrollLabel, className: cx("overflow-x-auto", wrapperClassName), children: _jsxs("table", { className: cx("w-full text-left text-sm", className), ...props, children: [caption ? (_jsx("caption", { id: captionId, className: hideCaption ? "sr-only" : `mb-2 text-left ${FIELD_LABEL}`, children: caption })) : null, children] }) }));
12
25
  }
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * their own "use client" directive, so this barrel is safe to import from Server Components.
6
6
  * @author David @dvhsh (https://dvh.sh)
7
7
  * @created Wed Sep 23, 2026
8
- * @modified Mon Sep 28, 2026
8
+ * @modified Sat Oct 3, 2026
9
9
  */
10
10
  export { AsyncButton, type AsyncButtonProps } from "./components/actions/AsyncButton.js";
11
11
  export { CopyButton, type CopyButtonProps } from "./components/actions/CopyButton.js";
@@ -50,6 +50,13 @@ export { JsonLd, type JsonLdProps } from "./components/meta/JsonLd.js";
50
50
  export { type BeatmapStatKey, BeatmapStats, type BeatmapStatsProps, } from "./components/osu/BeatmapStats.js";
51
51
  export { ModBadge, type ModBadgeProps } from "./components/osu/ModBadge.js";
52
52
  export { StarRating, type StarRatingProps } from "./components/osu/StarRating.js";
53
+ export { CommandPalette } from "./components/palette/CommandPalette.js";
54
+ export { CommandPaletteButton, type CommandPaletteButtonProps, } from "./components/palette/CommandPaletteButton.js";
55
+ export { evaluate, formatResult } from "./components/palette/calc.js";
56
+ export { type FuzzyMatch, fuzzyScore } from "./components/palette/fuzzy.js";
57
+ export { openCommandPalette } from "./components/palette/paletteEvents.js";
58
+ export { type SiteCommandsOptions, siteCommands } from "./components/palette/siteCommands.js";
59
+ export type { ArgChoice, ArgSpec, Command, CommandPaletteProps, Page, PaletteContext, Provider, } from "./components/palette/types.js";
53
60
  export { HeaderMenu, type HeaderMenuItem, type HeaderMenuProps, } from "./components/shell/HeaderMenu.js";
54
61
  export { HARUHIME_TOOLS, type HaruhimeTool, type HaruhimeToolId, type HaruhimeToolsOptions, haruhimeToolsColumn, } from "./components/shell/haruhimeTools.js";
55
62
  export { type LinkTabItem, LinkTabs, type LinkTabsProps } from "./components/shell/LinkTabs.js";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * their own "use client" directive, so this barrel is safe to import from Server Components.
6
6
  * @author David @dvhsh (https://dvh.sh)
7
7
  * @created Wed Sep 23, 2026
8
- * @modified Mon Sep 28, 2026
8
+ * @modified Sat Oct 3, 2026
9
9
  */
10
10
  // Actions
11
11
  export { AsyncButton } from "./components/actions/AsyncButton.js";
@@ -55,6 +55,13 @@ export { JsonLd } from "./components/meta/JsonLd.js";
55
55
  export { BeatmapStats, } from "./components/osu/BeatmapStats.js";
56
56
  export { ModBadge } from "./components/osu/ModBadge.js";
57
57
  export { StarRating } from "./components/osu/StarRating.js";
58
+ // Palette
59
+ export { CommandPalette } from "./components/palette/CommandPalette.js";
60
+ export { CommandPaletteButton, } from "./components/palette/CommandPaletteButton.js";
61
+ export { evaluate, formatResult } from "./components/palette/calc.js";
62
+ export { fuzzyScore } from "./components/palette/fuzzy.js";
63
+ export { openCommandPalette } from "./components/palette/paletteEvents.js";
64
+ export { siteCommands } from "./components/palette/siteCommands.js";
58
65
  // Shell
59
66
  export { HeaderMenu, } from "./components/shell/HeaderMenu.js";
60
67
  export { HARUHIME_TOOLS, haruhimeToolsColumn, } from "./components/shell/haruhimeTools.js";
package/dist/theme.css CHANGED
@@ -10,12 +10,12 @@
10
10
  * @import "@haruhimemoe/ui/theme.css";
11
11
  *
12
12
  * Set --hue on :root to recolor everything (default 333, pink). Some hues need --h2-l
13
- * (h2 lightness, default 45%) or --h1-l (h1 lightness, default 70%) moved to keep text at
14
- * 4.5:1; the README lists values. Load Nunito with next/font as the --font-nunito variable;
13
+ * (h2 lightness, default 45%) or --h1-l (h1 lightness, default 76%, which clears 4.5:1 on
14
+ * b5 at every hue) moved to keep text at 4.5:1; the README lists values. Load Nunito with next/font as the --font-nunito variable;
15
15
  * without it the sans stack falls back to the system font.
16
16
  * @author David @dvhsh (https://dvh.sh)
17
17
  * @created Wed Sep 23, 2026
18
- * @modified Wed Sep 23, 2026
18
+ * @modified Sat Oct 3, 2026
19
19
  */
20
20
 
21
21
  /* This file ships as dist/theme.css, so './' is the package's compiled components. */
@@ -39,7 +39,7 @@
39
39
  --color-c3: hsl(var(--hue) 40% 80%);
40
40
  --color-c4: hsl(var(--hue) 40% 70%);
41
41
 
42
- --color-h1: hsl(var(--hue) 100% var(--h1-l, 70%));
42
+ --color-h1: hsl(var(--hue) 100% var(--h1-l, 76%));
43
43
  --color-h2: hsl(var(--hue) 50% var(--h2-l, 45%));
44
44
 
45
45
  --font-sans: var(--font-nunito), ui-sans-serif, system-ui, sans-serif;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@haruhimemoe/ui",
3
- "version": "0.6.0",
4
- "description": "React components for the haruhime.moe osu! tools on Next.js: the osu!-web-style palette as a Tailwind theme, buttons, cards, form fields and confirms, filter controls, tables, osu! beatmap display pieces and the site header and footer.",
3
+ "version": "0.8.0",
4
+ "description": "React components for the haruhime.moe osu! tools on Next.js: the osu!-web-style palette as a Tailwind theme, buttons, cards, form fields and confirms, filter controls, tables, osu! beatmap display pieces, a command palette and the site header and footer.",
5
5
  "keywords": [
6
6
  "osu",
7
7
  "osu!",
@@ -50,11 +50,14 @@
50
50
  "build": "bun run clean && tsc -p tsconfig.build.json && node -e \"require('node:fs').copyFileSync('src/theme.css','dist/theme.css')\"",
51
51
  "check": "biome check .",
52
52
  "check:fix": "biome check --write .",
53
- "typecheck": "tsc --noEmit && tsc -p scripts/consumer-fixture/tsconfig.json",
53
+ "typecheck": "tsc --noEmit && tsc -p scripts/consumer-fixture/tsconfig.json && tsc -p playground/tsconfig.json",
54
54
  "test": "vitest run",
55
55
  "test:coverage": "vitest run --coverage",
56
56
  "test:watch": "vitest",
57
57
  "check:consumer": "node scripts/check-consumer.mjs",
58
+ "play": "next dev playground --webpack",
59
+ "play:build": "next build playground --webpack",
60
+ "play:axe": "node scripts/axe-playground.mjs",
58
61
  "prepublishOnly": "bun run check && bun run typecheck && bun run test && bun run build"
59
62
  },
60
63
  "dependencies": {
@@ -67,6 +70,7 @@
67
70
  "tailwindcss": ">=4.1.0 <5"
68
71
  },
69
72
  "devDependencies": {
73
+ "@axe-core/playwright": "4.13.0",
70
74
  "@biomejs/biome": "2.5.14",
71
75
  "@tailwindcss/postcss": "4.3.3",
72
76
  "@testing-library/dom": "10.4.2",
@@ -81,6 +85,7 @@
81
85
  "axe-core": "4.13.0",
82
86
  "jsdom": "30.1.1",
83
87
  "next": "16.3.6",
88
+ "playwright": "1.63.0",
84
89
  "react": "19.3.0",
85
90
  "react-dom": "19.3.0",
86
91
  "tailwindcss": "4.3.3",