@haruhimemoe/ui 0.2.0 → 0.4.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 (122) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/README.md +265 -29
  3. package/dist/components/actions/AsyncButton.d.ts +30 -0
  4. package/dist/components/actions/AsyncButton.js +48 -0
  5. package/dist/components/actions/CopyButton.d.ts +3 -3
  6. package/dist/components/actions/CopyButton.js +9 -12
  7. package/dist/components/actions/InlineConfirm.d.ts +45 -0
  8. package/dist/components/actions/InlineConfirm.js +80 -0
  9. package/dist/components/actions/Pagination.d.ts +40 -17
  10. package/dist/components/actions/Pagination.js +25 -18
  11. package/dist/components/actions/PaginationButton.d.ts +25 -0
  12. package/dist/components/actions/PaginationButton.js +23 -0
  13. package/dist/components/actions/PaginationStatus.d.ts +7 -4
  14. package/dist/components/actions/PaginationStatus.js +7 -5
  15. package/dist/components/actions/StatusOutput.d.ts +20 -0
  16. package/dist/components/actions/StatusOutput.js +9 -0
  17. package/dist/components/actions/pages.d.ts +21 -0
  18. package/dist/components/actions/pages.js +21 -0
  19. package/dist/components/actions/useLatestStatus.d.ts +26 -0
  20. package/dist/components/actions/useLatestStatus.js +32 -0
  21. package/dist/components/basics/AutoLink.d.ts +21 -0
  22. package/dist/components/basics/AutoLink.js +26 -0
  23. package/dist/components/basics/Badge.d.ts +22 -0
  24. package/dist/components/basics/Badge.js +18 -0
  25. package/dist/components/basics/ButtonLink.d.ts +5 -6
  26. package/dist/components/basics/ButtonLink.js +5 -11
  27. package/dist/components/basics/Card.d.ts +4 -3
  28. package/dist/components/basics/Card.js +3 -2
  29. package/dist/components/basics/Disclosure.d.ts +34 -0
  30. package/dist/components/basics/Disclosure.js +30 -0
  31. package/dist/components/basics/Notice.d.ts +2 -1
  32. package/dist/components/basics/TextLink.d.ts +21 -0
  33. package/dist/components/basics/TextLink.js +19 -0
  34. package/dist/components/basics/buttonStyles.d.ts +3 -1
  35. package/dist/components/basics/buttonStyles.js +1 -1
  36. package/dist/components/basics/cardStyles.d.ts +14 -0
  37. package/dist/components/basics/cardStyles.js +12 -0
  38. package/dist/components/basics/linkStyles.d.ts +23 -0
  39. package/dist/components/basics/linkStyles.js +23 -0
  40. package/dist/components/filters/Chip.d.ts +13 -5
  41. package/dist/components/filters/Chip.js +20 -11
  42. package/dist/components/filters/ChipGroup.d.ts +11 -12
  43. package/dist/components/filters/ChipGroup.js +5 -10
  44. package/dist/components/filters/ChoiceChips.d.ts +38 -0
  45. package/dist/components/filters/ChoiceChips.js +31 -0
  46. package/dist/components/filters/FilterPanel.d.ts +5 -4
  47. package/dist/components/filters/FilterPanel.js +5 -4
  48. package/dist/components/filters/FilterRow.d.ts +1 -1
  49. package/dist/components/filters/FilterRow.js +3 -2
  50. package/dist/components/filters/GroupFrame.d.ts +27 -0
  51. package/dist/components/filters/GroupFrame.js +27 -0
  52. package/dist/components/filters/RangeBox.d.ts +28 -0
  53. package/dist/components/filters/RangeBox.js +41 -0
  54. package/dist/components/filters/RangeSlider.d.ts +13 -14
  55. package/dist/components/filters/RangeSlider.js +40 -103
  56. package/dist/components/filters/chipStyles.d.ts +17 -0
  57. package/dist/components/filters/chipStyles.js +17 -0
  58. package/dist/components/filters/rangeMath.d.ts +106 -0
  59. package/dist/components/filters/rangeMath.js +146 -0
  60. package/dist/components/forms/Checkbox.d.ts +5 -4
  61. package/dist/components/forms/Checkbox.js +3 -3
  62. package/dist/components/forms/FieldFrame.d.ts +28 -2
  63. package/dist/components/forms/FieldFrame.js +14 -1
  64. package/dist/components/forms/RadioGroup.d.ts +46 -0
  65. package/dist/components/forms/RadioGroup.js +32 -0
  66. package/dist/components/forms/Select.d.ts +1 -1
  67. package/dist/components/forms/Select.js +2 -2
  68. package/dist/components/forms/TextInput.d.ts +1 -1
  69. package/dist/components/forms/TextInput.js +2 -2
  70. package/dist/components/forms/Textarea.d.ts +1 -1
  71. package/dist/components/forms/Textarea.js +2 -2
  72. package/dist/components/forms/TypeToConfirm.d.ts +40 -0
  73. package/dist/components/forms/TypeToConfirm.js +46 -0
  74. package/dist/components/forms/fieldStyles.d.ts +5 -2
  75. package/dist/components/forms/fieldStyles.js +5 -2
  76. package/dist/components/icons/DiscordIcon.d.ts +21 -0
  77. package/dist/components/icons/DiscordIcon.js +10 -0
  78. package/dist/components/{actions → meta}/JsonLd.d.ts +2 -2
  79. package/dist/components/osu/BeatmapStats.d.ts +36 -0
  80. package/dist/components/osu/BeatmapStats.js +44 -0
  81. package/dist/components/osu/ModBadge.d.ts +23 -0
  82. package/dist/components/osu/ModBadge.js +22 -0
  83. package/dist/components/osu/StarRating.d.ts +26 -0
  84. package/dist/components/osu/StarRating.js +17 -0
  85. package/dist/components/osu/starColors.d.ts +23 -0
  86. package/dist/components/osu/starColors.js +48 -0
  87. package/dist/components/shell/HeaderMenu.d.ts +39 -0
  88. package/dist/components/shell/HeaderMenu.js +58 -0
  89. package/dist/components/shell/LinkNote.d.ts +16 -0
  90. package/dist/components/shell/LinkNote.js +17 -0
  91. package/dist/components/shell/LinkTabs.d.ts +29 -0
  92. package/dist/components/shell/LinkTabs.js +15 -0
  93. package/dist/components/shell/NavItem.d.ts +1 -7
  94. package/dist/components/shell/NavItem.js +5 -10
  95. package/dist/components/shell/NavLinks.d.ts +1 -1
  96. package/dist/components/shell/NavLinks.js +3 -3
  97. package/dist/components/shell/NavListClient.d.ts +1 -1
  98. package/dist/components/shell/NavListClient.js +4 -4
  99. package/dist/components/shell/SiteFooter.d.ts +11 -6
  100. package/dist/components/shell/SiteFooter.js +18 -7
  101. package/dist/components/shell/links.d.ts +8 -1
  102. package/dist/components/shell/links.js +8 -1
  103. package/dist/components/tables/TBody.d.ts +16 -0
  104. package/dist/components/tables/TBody.js +10 -0
  105. package/dist/components/tables/THead.d.ts +16 -0
  106. package/dist/components/tables/THead.js +10 -0
  107. package/dist/components/tables/Table.d.ts +26 -0
  108. package/dist/components/tables/Table.js +12 -0
  109. package/dist/components/tables/Td.d.ts +19 -0
  110. package/dist/components/tables/Td.js +11 -0
  111. package/dist/components/tables/Th.d.ts +20 -0
  112. package/dist/components/tables/Th.js +11 -0
  113. package/dist/components/tables/tableStyles.d.ts +12 -0
  114. package/dist/components/tables/tableStyles.js +12 -0
  115. package/dist/index.d.ts +24 -2
  116. package/dist/index.js +28 -2
  117. package/dist/utils/href.d.ts +7 -4
  118. package/dist/utils/href.js +23 -7
  119. package/package.json +3 -3
  120. package/dist/components/shell/AutoLink.d.ts +0 -19
  121. package/dist/components/shell/AutoLink.js +0 -23
  122. /package/dist/components/{actions → meta}/JsonLd.js +0 -0
@@ -5,7 +5,7 @@
5
5
  * ever be the current page.
6
6
  * @author David @dvhsh (https://dvh.sh)
7
7
  * @created Wed Sep 23, 2026
8
- * @modified Thu Sep 24, 2026
8
+ * @modified Mon Sep 28, 2026
9
9
  */
10
10
  import { isExternalHref } from "../../utils/href.js";
11
11
  export { isExternalHref };
@@ -15,6 +15,13 @@ export type SiteLinkItem = {
15
15
  href?: string | undefined;
16
16
  note?: string | undefined;
17
17
  };
18
+ /**
19
+ * @function linkItemKey
20
+ * @param item {SiteLinkItem} a nav or footer entry
21
+ * @returns {string} its React key, from the label and the href together, so two entries with the
22
+ * same href (or two text-only entries) still get different keys
23
+ */
24
+ export declare const linkItemKey: (item: SiteLinkItem) => string;
18
25
  /**
19
26
  * @function canBeCurrent
20
27
  * @param href {string} a nav link target
@@ -5,10 +5,17 @@
5
5
  * ever be the current page.
6
6
  * @author David @dvhsh (https://dvh.sh)
7
7
  * @created Wed Sep 23, 2026
8
- * @modified Thu Sep 24, 2026
8
+ * @modified Mon Sep 28, 2026
9
9
  */
10
10
  import { isExternalHref } from "../../utils/href.js";
11
11
  export { isExternalHref };
12
+ /**
13
+ * @function linkItemKey
14
+ * @param item {SiteLinkItem} a nav or footer entry
15
+ * @returns {string} its React key, from the label and the href together, so two entries with the
16
+ * same href (or two text-only entries) still get different keys
17
+ */
18
+ export const linkItemKey = (item) => `${item.label} ${item.href ?? ""}`;
12
19
  const trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
13
20
  // The app path an href points at, without query, fragment or trailing slash. Undefined for an
14
21
  // href that leaves the app or is relative ("packs", "#main", "?page=2").
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @file src/components/tables/TBody.tsx
3
+ * @desc A table's body rows, each with a b4 rule above it. Server-safe.
4
+ * @author David @dvhsh (https://dvh.sh)
5
+ * @created Mon Sep 28, 2026
6
+ * @modified Mon Sep 28, 2026
7
+ */
8
+ import type { ComponentProps } from "react";
9
+ /** Every native `<tbody>` prop (including `ref`). */
10
+ export type TBodyProps = ComponentProps<"tbody">;
11
+ /**
12
+ * @function TBody
13
+ * @param props {TBodyProps} native tbody props; plain `<tr>`s as children
14
+ * @returns {JSX.Element} a `<tbody>` whose rows get a top border in b4
15
+ */
16
+ export declare function TBody({ className, ...props }: TBodyProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "../../utils/cx.js";
3
+ /**
4
+ * @function TBody
5
+ * @param props {TBodyProps} native tbody props; plain `<tr>`s as children
6
+ * @returns {JSX.Element} a `<tbody>` whose rows get a top border in b4
7
+ */
8
+ export function TBody({ className, ...props }) {
9
+ return _jsx("tbody", { className: cx("[&>tr]:border-b4 [&>tr]:border-t", className), ...props });
10
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @file src/components/tables/THead.tsx
3
+ * @desc A table's header rows in small muted capitals, the apps' column-heading look. Server-safe.
4
+ * @author David @dvhsh (https://dvh.sh)
5
+ * @created Mon Sep 28, 2026
6
+ * @modified Mon Sep 28, 2026
7
+ */
8
+ import type { ComponentProps } from "react";
9
+ /** Every native `<thead>` prop (including `ref`). */
10
+ export type THeadProps = ComponentProps<"thead">;
11
+ /**
12
+ * @function THead
13
+ * @param props {THeadProps} native thead props; the header `<tr>` as children
14
+ * @returns {JSX.Element} a `<thead>` in c3 at text-xs, uppercase
15
+ */
16
+ export declare function THead({ className, ...props }: THeadProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "../../utils/cx.js";
3
+ /**
4
+ * @function THead
5
+ * @param props {THeadProps} native thead props; the header `<tr>` as children
6
+ * @returns {JSX.Element} a `<thead>` in c3 at text-xs, uppercase
7
+ */
8
+ export function THead({ className, ...props }) {
9
+ return _jsx("thead", { className: cx("text-c3 text-xs uppercase", className), ...props });
10
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @file src/components/tables/Table.tsx
3
+ * @desc A data table on the apps' look: full width, small left-aligned text, in a wrapper that
4
+ * scrolls sideways on phones, with an optional caption (visible, or for screen readers only).
5
+ * Server-safe. Fill it with THead, TBody, Th and Td.
6
+ * @author David @dvhsh (https://dvh.sh)
7
+ * @created Mon Sep 28, 2026
8
+ * @modified Mon Sep 28, 2026
9
+ */
10
+ import type { ComponentProps, ReactNode } from "react";
11
+ /** Every native `<table>` prop (including `ref`), plus a caption and the wrapper's classes. */
12
+ export type TableProps = ComponentProps<"table"> & {
13
+ /** Names the table for screen readers. Shown above it unless `hideCaption`. */
14
+ caption?: ReactNode;
15
+ /** Keep the caption for screen readers only (`sr-only`), when a heading already shows. */
16
+ hideCaption?: boolean | undefined;
17
+ /** Classes for the wrapper `<div>` that scrolls sideways. */
18
+ wrapperClassName?: string | undefined;
19
+ };
20
+ /**
21
+ * @function Table
22
+ * @param props {TableProps} native table props (rows as children), a caption, hideCaption and
23
+ * wrapperClassName; `className` and `ref` go on the `<table>`
24
+ * @returns {JSX.Element} an `overflow-x-auto` `<div>` around the `<table>` and its caption
25
+ */
26
+ export declare function Table({ caption, hideCaption, wrapperClassName, className, children, ...props }: TableProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../../utils/cx.js";
3
+ import { FIELD_LABEL } from "../forms/fieldStyles.js";
4
+ /**
5
+ * @function Table
6
+ * @param props {TableProps} native table props (rows as children), a caption, hideCaption and
7
+ * wrapperClassName; `className` and `ref` go on the `<table>`
8
+ * @returns {JSX.Element} an `overflow-x-auto` `<div>` around the `<table>` and its caption
9
+ */
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] }) }));
12
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @file src/components/tables/Td.tsx
3
+ * @desc A data cell with the shared padding. Server-safe.
4
+ * @author David @dvhsh (https://dvh.sh)
5
+ * @created Mon Sep 28, 2026
6
+ * @modified Mon Sep 28, 2026
7
+ */
8
+ import type { ComponentProps } from "react";
9
+ /** Every native `<td>` prop (including `ref`), plus a flag for a number. */
10
+ export type TdProps = ComponentProps<"td"> & {
11
+ /** Line up digits (`tabular-nums`), for a number. */
12
+ numeric?: boolean | undefined;
13
+ };
14
+ /**
15
+ * @function Td
16
+ * @param props {TdProps} native td props, plus numeric
17
+ * @returns {JSX.Element} a `<td>` with the shared cell padding
18
+ */
19
+ export declare function Td({ numeric, className, ...props }: TdProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "../../utils/cx.js";
3
+ import { CELL, NUMERIC } from "./tableStyles.js";
4
+ /**
5
+ * @function Td
6
+ * @param props {TdProps} native td props, plus numeric
7
+ * @returns {JSX.Element} a `<td>` with the shared cell padding
8
+ */
9
+ export function Td({ numeric = false, className, ...props }) {
10
+ return _jsx("td", { className: cx(CELL, numeric && NUMERIC, className), ...props });
11
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @file src/components/tables/Th.tsx
3
+ * @desc A header cell: a column heading by default (scope col), or a row's heading (scope row,
4
+ * bold c1). Server-safe.
5
+ * @author David @dvhsh (https://dvh.sh)
6
+ * @created Mon Sep 28, 2026
7
+ * @modified Mon Sep 28, 2026
8
+ */
9
+ import type { ComponentProps } from "react";
10
+ /** Every native `<th>` prop (including `ref`), plus a flag for a numbers column. */
11
+ export type ThProps = ComponentProps<"th"> & {
12
+ /** Line up digits (`tabular-nums`), for a column of numbers. */
13
+ numeric?: boolean | undefined;
14
+ };
15
+ /**
16
+ * @function Th
17
+ * @param props {ThProps} native th props (`scope` defaults to "col"), plus numeric
18
+ * @returns {JSX.Element} a `<th>` with the shared cell padding; bold c1 when it heads a row
19
+ */
20
+ export declare function Th({ scope, numeric, className, ...props }: ThProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "../../utils/cx.js";
3
+ import { CELL, NUMERIC } from "./tableStyles.js";
4
+ /**
5
+ * @function Th
6
+ * @param props {ThProps} native th props (`scope` defaults to "col"), plus numeric
7
+ * @returns {JSX.Element} a `<th>` with the shared cell padding; bold c1 when it heads a row
8
+ */
9
+ export function Th({ scope = "col", numeric = false, className, ...props }) {
10
+ return (_jsx("th", { scope: scope, className: cx(CELL, scope === "row" && "font-bold text-c1", numeric && NUMERIC, className), ...props }));
11
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file src/components/tables/tableStyles.ts
3
+ * @desc The cell padding Th and Td share (internal): room on the right between columns, none after
4
+ * the last one, as the apps' tables have it.
5
+ * @author David @dvhsh (https://dvh.sh)
6
+ * @created Mon Sep 28, 2026
7
+ * @modified Mon Sep 28, 2026
8
+ */
9
+ /** A header or data cell: py-2 and pr-3, with no right padding on the row's last cell. */
10
+ export declare const CELL = "py-2 pr-3 last:pr-0";
11
+ /** Numbers line up in a column. */
12
+ export declare const NUMERIC = "tabular-nums";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file src/components/tables/tableStyles.ts
3
+ * @desc The cell padding Th and Td share (internal): room on the right between columns, none after
4
+ * the last one, as the apps' tables have it.
5
+ * @author David @dvhsh (https://dvh.sh)
6
+ * @created Mon Sep 28, 2026
7
+ * @modified Mon Sep 28, 2026
8
+ */
9
+ /** A header or data cell: py-2 and pr-3, with no right padding on the row's last cell. */
10
+ export const CELL = "py-2 pr-3 last:pr-0";
11
+ /** Numbers line up in a column. */
12
+ export const NUMERIC = "tabular-nums";
package/dist/index.d.ts CHANGED
@@ -5,34 +5,56 @@
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 Wed Sep 23, 2026
8
+ * @modified Mon Sep 28, 2026
9
9
  */
10
+ export { AsyncButton, type AsyncButtonProps } from "./components/actions/AsyncButton.js";
10
11
  export { CopyButton, type CopyButtonProps } from "./components/actions/CopyButton.js";
11
- export { JsonLd, type JsonLdProps } from "./components/actions/JsonLd.js";
12
+ export { InlineConfirm, type InlineConfirmProps } from "./components/actions/InlineConfirm.js";
12
13
  export { Pagination, type PaginationProps } from "./components/actions/Pagination.js";
14
+ export { Badge, type BadgeProps, type BadgeTone } from "./components/basics/Badge.js";
13
15
  export { Button, type ButtonProps } from "./components/basics/Button.js";
14
16
  export { ButtonLink, type ButtonLinkProps } from "./components/basics/ButtonLink.js";
15
17
  export { type ButtonClassOptions, type ButtonSize, type ButtonVariant, buttonClasses, } from "./components/basics/buttonStyles.js";
16
18
  export { Card, type CardProps } from "./components/basics/Card.js";
19
+ export type { HeadingLevel } from "./components/basics/cardStyles.js";
20
+ export { Disclosure, type DisclosureProps } from "./components/basics/Disclosure.js";
21
+ export { type LinkClassOptions, linkClasses, type TextLinkVariant, } from "./components/basics/linkStyles.js";
17
22
  export { Notice, type NoticeProps, type NoticeTone } from "./components/basics/Notice.js";
18
23
  export { PageHeader, type PageHeaderProps } from "./components/basics/PageHeader.js";
19
24
  export { Prose, type ProseProps } from "./components/basics/Prose.js";
25
+ export { TextLink, type TextLinkProps } from "./components/basics/TextLink.js";
20
26
  export { Chip, type ChipProps } from "./components/filters/Chip.js";
21
27
  export { ChipGroup, type ChipGroupProps, type ChipOption } from "./components/filters/ChipGroup.js";
28
+ export { type ChoiceChipOption, ChoiceChips, type ChoiceChipsProps, } from "./components/filters/ChoiceChips.js";
22
29
  export { FilterPanel, type FilterPanelProps } from "./components/filters/FilterPanel.js";
23
30
  export { FilterRow, type FilterRowProps } from "./components/filters/FilterRow.js";
24
31
  export { RangeSlider, type RangeSliderProps, type RangeSliderValue, } from "./components/filters/RangeSlider.js";
25
32
  export { Checkbox, type CheckboxProps } from "./components/forms/Checkbox.js";
26
33
  export type { FieldProps } from "./components/forms/FieldFrame.js";
27
34
  export { fieldClasses } from "./components/forms/fieldStyles.js";
35
+ export { RadioGroup, type RadioGroupProps, type RadioOption, } from "./components/forms/RadioGroup.js";
28
36
  export { Select, type SelectProps } from "./components/forms/Select.js";
29
37
  export { Textarea, type TextareaProps } from "./components/forms/Textarea.js";
30
38
  export { TextInput, type TextInputProps } from "./components/forms/TextInput.js";
39
+ export { TypeToConfirm, type TypeToConfirmProps } from "./components/forms/TypeToConfirm.js";
40
+ export { DiscordIcon, type DiscordIconProps } from "./components/icons/DiscordIcon.js";
31
41
  export { GitHubIcon, type GitHubIconProps } from "./components/icons/GitHubIcon.js";
32
42
  export { HaruhimeWordmark, type HaruhimeWordmarkProps, } from "./components/icons/HaruhimeWordmark.js";
33
43
  export { HaruhimeWordmarkLink, type HaruhimeWordmarkLinkProps, } from "./components/icons/HaruhimeWordmarkLink.js";
44
+ export { JsonLd, type JsonLdProps } from "./components/meta/JsonLd.js";
45
+ export { type BeatmapStatKey, BeatmapStats, type BeatmapStatsProps, } from "./components/osu/BeatmapStats.js";
46
+ export { ModBadge, type ModBadgeProps } from "./components/osu/ModBadge.js";
47
+ export { StarRating, type StarRatingProps } from "./components/osu/StarRating.js";
48
+ export { HeaderMenu, type HeaderMenuItem, type HeaderMenuProps, } from "./components/shell/HeaderMenu.js";
49
+ export { type LinkTabItem, LinkTabs, type LinkTabsProps } from "./components/shell/LinkTabs.js";
34
50
  export type { SiteLinkItem } from "./components/shell/links.js";
35
51
  export { NavLinks, type NavLinksProps, type SiteNavAlign } from "./components/shell/NavLinks.js";
36
52
  export { PageShell, type PageShellProps } from "./components/shell/PageShell.js";
37
53
  export { SiteFooter, type SiteFooterColumn, type SiteFooterProps, } from "./components/shell/SiteFooter.js";
38
54
  export { SiteHeader, type SiteHeaderProps } from "./components/shell/SiteHeader.js";
55
+ export { Table, type TableProps } from "./components/tables/Table.js";
56
+ export { TBody, type TBodyProps } from "./components/tables/TBody.js";
57
+ export { Td, type TdProps } from "./components/tables/Td.js";
58
+ export { THead, type THeadProps } from "./components/tables/THead.js";
59
+ export { Th, type ThProps } from "./components/tables/Th.js";
60
+ export { type ClassValue, cx } from "./utils/cx.js";
package/dist/index.js CHANGED
@@ -5,37 +5,63 @@
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 Wed Sep 23, 2026
8
+ * @modified Mon Sep 28, 2026
9
9
  */
10
10
  // Actions
11
+ export { AsyncButton } from "./components/actions/AsyncButton.js";
11
12
  export { CopyButton } from "./components/actions/CopyButton.js";
12
- export { JsonLd } from "./components/actions/JsonLd.js";
13
+ export { InlineConfirm } from "./components/actions/InlineConfirm.js";
13
14
  export { Pagination } from "./components/actions/Pagination.js";
14
15
  // Basics
16
+ export { Badge } from "./components/basics/Badge.js";
15
17
  export { Button } from "./components/basics/Button.js";
16
18
  export { ButtonLink } from "./components/basics/ButtonLink.js";
17
19
  export { buttonClasses, } from "./components/basics/buttonStyles.js";
18
20
  export { Card } from "./components/basics/Card.js";
21
+ export { Disclosure } from "./components/basics/Disclosure.js";
22
+ export { linkClasses, } from "./components/basics/linkStyles.js";
19
23
  export { Notice } from "./components/basics/Notice.js";
20
24
  export { PageHeader } from "./components/basics/PageHeader.js";
21
25
  export { Prose } from "./components/basics/Prose.js";
26
+ export { TextLink } from "./components/basics/TextLink.js";
22
27
  // Filters
23
28
  export { Chip } from "./components/filters/Chip.js";
24
29
  export { ChipGroup } from "./components/filters/ChipGroup.js";
30
+ export { ChoiceChips, } from "./components/filters/ChoiceChips.js";
25
31
  export { FilterPanel } from "./components/filters/FilterPanel.js";
26
32
  export { FilterRow } from "./components/filters/FilterRow.js";
27
33
  export { RangeSlider, } from "./components/filters/RangeSlider.js";
28
34
  // Forms
29
35
  export { Checkbox } from "./components/forms/Checkbox.js";
30
36
  export { fieldClasses } from "./components/forms/fieldStyles.js";
37
+ export { RadioGroup, } from "./components/forms/RadioGroup.js";
31
38
  export { Select } from "./components/forms/Select.js";
32
39
  export { Textarea } from "./components/forms/Textarea.js";
33
40
  export { TextInput } from "./components/forms/TextInput.js";
41
+ export { TypeToConfirm } from "./components/forms/TypeToConfirm.js";
34
42
  // Icons
43
+ export { DiscordIcon } from "./components/icons/DiscordIcon.js";
35
44
  export { GitHubIcon } from "./components/icons/GitHubIcon.js";
36
45
  export { HaruhimeWordmark, } from "./components/icons/HaruhimeWordmark.js";
37
46
  export { HaruhimeWordmarkLink, } from "./components/icons/HaruhimeWordmarkLink.js";
47
+ // Meta
48
+ export { JsonLd } from "./components/meta/JsonLd.js";
49
+ // osu!
50
+ export { BeatmapStats, } from "./components/osu/BeatmapStats.js";
51
+ export { ModBadge } from "./components/osu/ModBadge.js";
52
+ export { StarRating } from "./components/osu/StarRating.js";
53
+ // Shell
54
+ export { HeaderMenu, } from "./components/shell/HeaderMenu.js";
55
+ export { LinkTabs } from "./components/shell/LinkTabs.js";
38
56
  export { NavLinks } from "./components/shell/NavLinks.js";
39
57
  export { PageShell } from "./components/shell/PageShell.js";
40
58
  export { SiteFooter, } from "./components/shell/SiteFooter.js";
41
59
  export { SiteHeader } from "./components/shell/SiteHeader.js";
60
+ // Tables
61
+ export { Table } from "./components/tables/Table.js";
62
+ export { TBody } from "./components/tables/TBody.js";
63
+ export { Td } from "./components/tables/Td.js";
64
+ export { THead } from "./components/tables/THead.js";
65
+ export { Th } from "./components/tables/Th.js";
66
+ // Utilities
67
+ export { cx } from "./utils/cx.js";
@@ -1,15 +1,18 @@
1
1
  /**
2
2
  * @file src/utils/href.ts
3
3
  * @desc Tells hrefs that leave the app (a URL scheme or a protocol-relative //host) from paths
4
- * inside it, so links can pick a plain <a> or next/link.
4
+ * inside it, so links can pick a plain <a> or next/link. It reads an href the way a
5
+ * browser's URL parser does: leading spaces and control characters don't count, tabs and
6
+ * newlines anywhere are dropped, and a backslash counts as a slash (`/\host` is `//host`).
5
7
  * @author David @dvhsh (https://dvh.sh)
6
8
  * @created Wed Sep 23, 2026
7
- * @modified Wed Sep 23, 2026
9
+ * @modified Mon Sep 28, 2026
8
10
  */
9
11
  /**
10
12
  * @function isExternalHref
11
13
  * @param href {string} a link target
12
- * @returns {boolean} true when the href has a scheme or starts with `//`, so it leaves the app
13
- * and should be a plain `<a>` instead of `next/link`
14
+ * @returns {boolean} true when a browser reads the href as a scheme or a `//host` (so also
15
+ * `/\host`, `\\host` and `" https://host"`): it leaves the app and should be a plain
16
+ * `<a>` instead of `next/link`
14
17
  */
15
18
  export declare const isExternalHref: (href: string) => boolean;
@@ -1,17 +1,33 @@
1
1
  /**
2
2
  * @file src/utils/href.ts
3
3
  * @desc Tells hrefs that leave the app (a URL scheme or a protocol-relative //host) from paths
4
- * inside it, so links can pick a plain <a> or next/link.
4
+ * inside it, so links can pick a plain <a> or next/link. It reads an href the way a
5
+ * browser's URL parser does: leading spaces and control characters don't count, tabs and
6
+ * newlines anywhere are dropped, and a backslash counts as a slash (`/\host` is `//host`).
5
7
  * @author David @dvhsh (https://dvh.sh)
6
8
  * @created Wed Sep 23, 2026
7
- * @modified Wed Sep 23, 2026
9
+ * @modified Mon Sep 28, 2026
8
10
  */
9
- // A URL scheme ("https:", "mailto:") or a protocol-relative "//host".
10
- const EXTERNAL = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
11
+ // A URL scheme ("https:", "mailto:") or a protocol-relative "//host", with "\" read as "/".
12
+ const EXTERNAL = /^(?:[a-z][a-z\d+.-]*:|[/\\]{2})/i;
13
+ /**
14
+ * @function parsedStart
15
+ * @param href {string} a link target
16
+ * @returns {string} the href as the URL parser starts reading it: every tab, line feed and
17
+ * carriage return removed, then the leading C0 controls and spaces
18
+ */
19
+ const parsedStart = (href) => {
20
+ const text = href.replace(/[\t\n\r]/g, "");
21
+ let start = 0;
22
+ while (start < text.length && text.charCodeAt(start) <= 0x20)
23
+ start++;
24
+ return text.slice(start);
25
+ };
11
26
  /**
12
27
  * @function isExternalHref
13
28
  * @param href {string} a link target
14
- * @returns {boolean} true when the href has a scheme or starts with `//`, so it leaves the app
15
- * and should be a plain `<a>` instead of `next/link`
29
+ * @returns {boolean} true when a browser reads the href as a scheme or a `//host` (so also
30
+ * `/\host`, `\\host` and `" https://host"`): it leaves the app and should be a plain
31
+ * `<a>` instead of `next/link`
16
32
  */
17
- export const isExternalHref = (href) => EXTERNAL.test(href);
33
+ export const isExternalHref = (href) => EXTERNAL.test(parsedStart(href));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@haruhimemoe/ui",
3
- "version": "0.2.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, filter controls and the site header and footer.",
3
+ "version": "0.4.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.",
5
5
  "keywords": [
6
6
  "osu",
7
7
  "osu!",
@@ -50,7 +50,7 @@
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",
53
+ "typecheck": "tsc --noEmit && tsc -p scripts/consumer-fixture/tsconfig.json",
54
54
  "test": "vitest run",
55
55
  "test:coverage": "vitest run --coverage",
56
56
  "test:watch": "vitest",
@@ -1,19 +0,0 @@
1
- /**
2
- * @file src/components/shell/AutoLink.tsx
3
- * @desc A link that picks its element from the href: `next/link` for paths inside the app, a
4
- * plain `<a>` for anything with a scheme (https:, mailto:) or a protocol-relative URL.
5
- * @author David @dvhsh (https://dvh.sh)
6
- * @created Wed Sep 23, 2026
7
- * @modified Wed Sep 23, 2026
8
- */
9
- import type { ComponentProps } from "react";
10
- /** Every native `<a>` prop (including `ref`), with `href` required and a plain string. */
11
- export type AutoLinkProps = Omit<ComponentProps<"a">, "href"> & {
12
- href: string;
13
- };
14
- /**
15
- * @function AutoLink
16
- * @param props {AutoLinkProps} native anchor props with a string href
17
- * @returns {JSX.Element} a `next/link` for internal paths, a plain `<a>` for external URLs
18
- */
19
- export declare function AutoLink({ href, ...props }: AutoLinkProps): import("react").JSX.Element;
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- /**
3
- * @file src/components/shell/AutoLink.tsx
4
- * @desc A link that picks its element from the href: `next/link` for paths inside the app, a
5
- * plain `<a>` for anything with a scheme (https:, mailto:) or a protocol-relative URL.
6
- * @author David @dvhsh (https://dvh.sh)
7
- * @created Wed Sep 23, 2026
8
- * @modified Wed Sep 23, 2026
9
- */
10
- import Link from "next/link.js";
11
- import { isExternalHref } from "./links.js";
12
- /**
13
- * @function AutoLink
14
- * @param props {AutoLinkProps} native anchor props with a string href
15
- * @returns {JSX.Element} a `next/link` for internal paths, a plain `<a>` for external URLs
16
- */
17
- export function AutoLink({ href, ...props }) {
18
- if (isExternalHref(href))
19
- return _jsx("a", { href: href, ...props });
20
- // Link takes every anchor prop, but types a few handlers (onClick, onMouseEnter) without
21
- // `| undefined`, which exactOptionalPropertyTypes rejects for a spread of optional props.
22
- return _jsx(Link, { href: href, ...props });
23
- }
File without changes