@zentauri-ui/zentauri-components 1.4.41 → 1.4.61
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/README.md +64 -1
- package/cli/registry.json +3 -1
- package/dist/ui/search/filter-search-suggestions.d.ts +15 -0
- package/dist/ui/search/filter-search-suggestions.d.ts.map +1 -0
- package/dist/ui/search/index.d.ts +7 -0
- package/dist/ui/search/index.d.ts.map +1 -0
- package/dist/ui/search/search-bar.d.ts +6 -0
- package/dist/ui/search/search-bar.d.ts.map +1 -0
- package/dist/ui/search/search-suggestion-list.d.ts +6 -0
- package/dist/ui/search/search-suggestion-list.d.ts.map +1 -0
- package/dist/ui/search/search-suggestion-utils.d.ts +6 -0
- package/dist/ui/search/search-suggestion-utils.d.ts.map +1 -0
- package/dist/ui/search/types.d.ts +44 -0
- package/dist/ui/search/types.d.ts.map +1 -0
- package/dist/ui/search.js +199 -0
- package/dist/ui/search.js.map +1 -0
- package/dist/ui/search.mjs +194 -0
- package/dist/ui/search.mjs.map +1 -0
- package/dist/ui/typography/blockquote-base.d.ts +6 -0
- package/dist/ui/typography/blockquote-base.d.ts.map +1 -0
- package/dist/ui/typography/blockquote.d.ts +6 -0
- package/dist/ui/typography/blockquote.d.ts.map +1 -0
- package/dist/ui/typography/code-block-base.d.ts +6 -0
- package/dist/ui/typography/code-block-base.d.ts.map +1 -0
- package/dist/ui/typography/code-block.d.ts +6 -0
- package/dist/ui/typography/code-block.d.ts.map +1 -0
- package/dist/ui/typography/heading-base.d.ts +6 -0
- package/dist/ui/typography/heading-base.d.ts.map +1 -0
- package/dist/ui/typography/heading.d.ts +6 -0
- package/dist/ui/typography/heading.d.ts.map +1 -0
- package/dist/ui/typography/index.d.ts +9 -0
- package/dist/ui/typography/index.d.ts.map +1 -0
- package/dist/ui/typography/inline-code-base.d.ts +6 -0
- package/dist/ui/typography/inline-code-base.d.ts.map +1 -0
- package/dist/ui/typography/inline-code.d.ts +6 -0
- package/dist/ui/typography/inline-code.d.ts.map +1 -0
- package/dist/ui/typography/list-base.d.ts +10 -0
- package/dist/ui/typography/list-base.d.ts.map +1 -0
- package/dist/ui/typography/list.d.ts +12 -0
- package/dist/ui/typography/list.d.ts.map +1 -0
- package/dist/ui/typography/text-base.d.ts +6 -0
- package/dist/ui/typography/text-base.d.ts.map +1 -0
- package/dist/ui/typography/text.d.ts +6 -0
- package/dist/ui/typography/text.d.ts.map +1 -0
- package/dist/ui/typography/types.d.ts +56 -0
- package/dist/ui/typography/types.d.ts.map +1 -0
- package/dist/ui/typography/variants.d.ts +16 -0
- package/dist/ui/typography/variants.d.ts.map +1 -0
- package/dist/ui/typography.js +334 -0
- package/dist/ui/typography.js.map +1 -0
- package/dist/ui/typography.mjs +321 -0
- package/dist/ui/typography.mjs.map +1 -0
- package/package.json +3 -3
- package/src/ui/search/filter-search-suggestions.test.ts +48 -0
- package/src/ui/search/filter-search-suggestions.ts +43 -0
- package/src/ui/search/index.ts +11 -0
- package/src/ui/search/search-bar.tsx +83 -0
- package/src/ui/search/search-suggestion-list.tsx +103 -0
- package/src/ui/search/search-suggestion-utils.test.ts +9 -0
- package/src/ui/search/search-suggestion-utils.ts +8 -0
- package/src/ui/search/types.ts +52 -0
- package/src/ui/typography/blockquote-base.tsx +39 -0
- package/src/ui/typography/blockquote.tsx +8 -0
- package/src/ui/typography/code-block-base.tsx +37 -0
- package/src/ui/typography/code-block.tsx +8 -0
- package/src/ui/typography/heading-base.tsx +59 -0
- package/src/ui/typography/heading.tsx +8 -0
- package/src/ui/typography/index.ts +28 -0
- package/src/ui/typography/inline-code-base.tsx +27 -0
- package/src/ui/typography/inline-code.tsx +8 -0
- package/src/ui/typography/list-base.tsx +88 -0
- package/src/ui/typography/list.tsx +15 -0
- package/src/ui/typography/text-base.tsx +43 -0
- package/src/ui/typography/text.tsx +8 -0
- package/src/ui/typography/types.ts +90 -0
- package/src/ui/typography/typography.test.tsx +80 -0
- package/src/ui/typography/variants.ts +72 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
import { searchSuggestionOptionDomId } from "./search-suggestion-utils";
|
|
7
|
+
|
|
8
|
+
import type { SearchSuggestionListProps } from "./types";
|
|
9
|
+
|
|
10
|
+
const rowClassName =
|
|
11
|
+
"flex w-full flex-col gap-0.5 rounded-lg px-3 py-2.5 text-left text-sm transition-colors hover:bg-white/5 focus-visible:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400/50";
|
|
12
|
+
|
|
13
|
+
export function SearchSuggestionList({
|
|
14
|
+
items,
|
|
15
|
+
onSelect,
|
|
16
|
+
activeId,
|
|
17
|
+
onActiveIdChange,
|
|
18
|
+
listboxId,
|
|
19
|
+
className,
|
|
20
|
+
listClassName,
|
|
21
|
+
emptyLabel,
|
|
22
|
+
}: SearchSuggestionListProps) {
|
|
23
|
+
if (items.length === 0) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
data-slot="search-suggestion-list-empty"
|
|
27
|
+
className={cn("px-1 py-6 text-center text-sm text-slate-500", className)}
|
|
28
|
+
>
|
|
29
|
+
{emptyLabel ?? "No matches."}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const useListbox = Boolean(listboxId);
|
|
35
|
+
|
|
36
|
+
const rows: Array<{
|
|
37
|
+
item: (typeof items)[number];
|
|
38
|
+
showGroup: boolean;
|
|
39
|
+
}> = [];
|
|
40
|
+
let lastGroupSeen: string | undefined;
|
|
41
|
+
for (const item of items) {
|
|
42
|
+
const showGroup = Boolean(item.group && item.group !== lastGroupSeen);
|
|
43
|
+
if (item.group) {
|
|
44
|
+
lastGroupSeen = item.group;
|
|
45
|
+
}
|
|
46
|
+
rows.push({ item, showGroup });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<nav
|
|
51
|
+
data-slot="search-suggestion-list"
|
|
52
|
+
aria-label="Search results"
|
|
53
|
+
className={cn("flex max-h-[min(50vh,360px)] flex-col gap-1 overflow-y-auto pr-1", className)}
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
{...(useListbox
|
|
57
|
+
? {
|
|
58
|
+
id: listboxId,
|
|
59
|
+
role: "listbox" as const,
|
|
60
|
+
}
|
|
61
|
+
: {})}
|
|
62
|
+
className={cn("flex flex-col gap-0.5", listClassName)}
|
|
63
|
+
>
|
|
64
|
+
{rows.map(({ item, showGroup }) => {
|
|
65
|
+
const isActive = activeId === item.id;
|
|
66
|
+
const optionDomId =
|
|
67
|
+
useListbox && listboxId ? searchSuggestionOptionDomId(listboxId, item.id) : undefined;
|
|
68
|
+
return (
|
|
69
|
+
<Fragment key={item.id}>
|
|
70
|
+
{showGroup ? (
|
|
71
|
+
<div
|
|
72
|
+
role="presentation"
|
|
73
|
+
className="sticky top-0 z-1 bg-slate-950/95 px-2 pb-1 pt-2 text-xs font-semibold uppercase tracking-wide text-slate-500 backdrop-blur-sm"
|
|
74
|
+
>
|
|
75
|
+
{item.group}
|
|
76
|
+
</div>
|
|
77
|
+
) : null}
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
id={optionDomId}
|
|
81
|
+
role={useListbox ? "option" : undefined}
|
|
82
|
+
aria-selected={useListbox ? isActive : undefined}
|
|
83
|
+
data-active={isActive ? "" : undefined}
|
|
84
|
+
className={cn(rowClassName, isActive ? "bg-white/5" : null)}
|
|
85
|
+
onMouseEnter={() => onActiveIdChange?.(item.id)}
|
|
86
|
+
onFocus={() => onActiveIdChange?.(item.id)}
|
|
87
|
+
onClick={() => onSelect(item.id)}
|
|
88
|
+
>
|
|
89
|
+
<span className="font-medium text-slate-100">{item.label}</span>
|
|
90
|
+
{item.description ? (
|
|
91
|
+
<span className="truncate text-xs text-slate-500">{item.description}</span>
|
|
92
|
+
) : null}
|
|
93
|
+
</button>
|
|
94
|
+
</Fragment>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
</div>
|
|
98
|
+
</nav>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
SearchSuggestionList.displayName = "SearchSuggestionList";
|
|
103
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { searchSuggestionOptionDomId } from "./search-suggestion-utils";
|
|
4
|
+
|
|
5
|
+
describe("searchSuggestionOptionDomId", () => {
|
|
6
|
+
it("prefixes listbox id and sanitizes item id", () => {
|
|
7
|
+
expect(searchSuggestionOptionDomId(":lb:", "/a/b")).toBe(":lb:_opt__a_b");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a stable DOM id for a listbox option so `aria-activedescendant` on the combobox
|
|
3
|
+
* input can reference it. Safe for href-like `itemId` strings.
|
|
4
|
+
*/
|
|
5
|
+
export function searchSuggestionOptionDomId(listboxId: string, itemId: string): string {
|
|
6
|
+
const safe = itemId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
7
|
+
return `${listboxId}_opt_${safe}`;
|
|
8
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, ReactNode, Ref } from "react";
|
|
2
|
+
|
|
3
|
+
import type { VariantProps } from "class-variance-authority";
|
|
4
|
+
|
|
5
|
+
import type { inputVariants } from "../inputs/variants";
|
|
6
|
+
|
|
7
|
+
export type SearchBarProps = Omit<
|
|
8
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
9
|
+
"size" | "children" | "role"
|
|
10
|
+
> & {
|
|
11
|
+
value: string;
|
|
12
|
+
onValueChange?: (value: string) => void;
|
|
13
|
+
leadingSlot?: ReactNode;
|
|
14
|
+
inputClassName?: string;
|
|
15
|
+
appearance?: VariantProps<typeof inputVariants>["appearance"];
|
|
16
|
+
inputSize?: VariantProps<typeof inputVariants>["size"];
|
|
17
|
+
ring?: VariantProps<typeof inputVariants>["ring"];
|
|
18
|
+
/** When set, the input exposes combobox semantics wired to a `role="listbox"` with this id. */
|
|
19
|
+
comboboxListboxId?: string;
|
|
20
|
+
/** Element id of the active option (from `searchSuggestionOptionDomId`) for `aria-activedescendant`. */
|
|
21
|
+
comboboxActiveOptionId?: string;
|
|
22
|
+
/** Whether the suggestion list is visibly expanded (controls `aria-expanded`). */
|
|
23
|
+
comboboxExpanded?: boolean;
|
|
24
|
+
ref?: Ref<HTMLInputElement>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type SearchSuggestionItem = {
|
|
28
|
+
id: string;
|
|
29
|
+
label: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
group?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type SearchSuggestionListProps = {
|
|
35
|
+
items: readonly SearchSuggestionItem[];
|
|
36
|
+
onSelect: (id: string) => void;
|
|
37
|
+
activeId?: string;
|
|
38
|
+
onActiveIdChange?: (id: string | undefined) => void;
|
|
39
|
+
/** Pass the same id as `comboboxListboxId` on `SearchBar` for ARIA wiring. */
|
|
40
|
+
listboxId?: string;
|
|
41
|
+
className?: string;
|
|
42
|
+
listClassName?: string;
|
|
43
|
+
emptyLabel?: ReactNode;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type SearchFilterable = {
|
|
47
|
+
id: string;
|
|
48
|
+
label: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
keywords?: readonly string[];
|
|
51
|
+
href?: string;
|
|
52
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
import type { BlockquoteProps } from "./types";
|
|
6
|
+
import { typographyToneVariants } from "./variants";
|
|
7
|
+
|
|
8
|
+
export const BlockquoteBase = (props: BlockquoteProps) => {
|
|
9
|
+
const {
|
|
10
|
+
tone,
|
|
11
|
+
attribution,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
ref,
|
|
15
|
+
...rest
|
|
16
|
+
} = props;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<blockquote
|
|
20
|
+
ref={ref}
|
|
21
|
+
data-slot="typography-blockquote"
|
|
22
|
+
className={cn(
|
|
23
|
+
typographyToneVariants({ tone }),
|
|
24
|
+
"border-l-4 py-1 pl-4 italic",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
29
|
+
<div className="space-y-2 leading-relaxed">{children}</div>
|
|
30
|
+
{attribution ? (
|
|
31
|
+
<footer className="mt-3 text-sm not-italic">
|
|
32
|
+
<cite>{attribution}</cite>
|
|
33
|
+
</footer>
|
|
34
|
+
) : null}
|
|
35
|
+
</blockquote>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
BlockquoteBase.displayName = "Blockquote";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
import type { CodeBlockProps } from "./types";
|
|
6
|
+
import { typographyToneVariants } from "./variants";
|
|
7
|
+
|
|
8
|
+
export const CodeBlockBase = (props: CodeBlockProps) => {
|
|
9
|
+
const {
|
|
10
|
+
tone,
|
|
11
|
+
language,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
ref,
|
|
15
|
+
...rest
|
|
16
|
+
} = props;
|
|
17
|
+
|
|
18
|
+
const ariaLabel = language ? `Code sample (${language})` : "Code sample";
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<pre
|
|
22
|
+
ref={ref}
|
|
23
|
+
data-slot="typography-code-block"
|
|
24
|
+
aria-label={ariaLabel}
|
|
25
|
+
className={cn(
|
|
26
|
+
typographyToneVariants({ tone }),
|
|
27
|
+
"overflow-x-auto rounded-xl border border-white/10 bg-slate-950/80 p-4 text-sm leading-relaxed shadow-inner shadow-slate-950/40",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...rest}
|
|
31
|
+
>
|
|
32
|
+
<code className="font-mono text-[0.95em] whitespace-pre-wrap wrap-break-word">{children}</code>
|
|
33
|
+
</pre>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
CodeBlockBase.displayName = "CodeBlock";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
import type { HeadingProps } from "./types";
|
|
6
|
+
import {
|
|
7
|
+
headingLevelVariants,
|
|
8
|
+
typographyToneVariants,
|
|
9
|
+
} from "./variants";
|
|
10
|
+
|
|
11
|
+
const HEADING_TAGS = {
|
|
12
|
+
1: "h1",
|
|
13
|
+
2: "h2",
|
|
14
|
+
3: "h3",
|
|
15
|
+
4: "h4",
|
|
16
|
+
5: "h5",
|
|
17
|
+
6: "h6",
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export const HeadingBase = (props: HeadingProps) => {
|
|
21
|
+
const {
|
|
22
|
+
level,
|
|
23
|
+
displayLevel,
|
|
24
|
+
tone,
|
|
25
|
+
bold,
|
|
26
|
+
italic,
|
|
27
|
+
underline,
|
|
28
|
+
strikethrough,
|
|
29
|
+
ref,
|
|
30
|
+
className,
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
} = props;
|
|
34
|
+
|
|
35
|
+
const Tag = HEADING_TAGS[level];
|
|
36
|
+
const scale = displayLevel ?? level;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Tag
|
|
40
|
+
ref={ref}
|
|
41
|
+
data-slot="typography-heading"
|
|
42
|
+
data-level={level}
|
|
43
|
+
className={cn(
|
|
44
|
+
typographyToneVariants({ tone }),
|
|
45
|
+
headingLevelVariants({ level: scale }),
|
|
46
|
+
bold && "font-bold",
|
|
47
|
+
italic && "italic",
|
|
48
|
+
underline && "underline underline-offset-4",
|
|
49
|
+
strikethrough && "line-through",
|
|
50
|
+
className,
|
|
51
|
+
)}
|
|
52
|
+
{...rest}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</Tag>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
HeadingBase.displayName = "Heading";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { Heading } from "./heading";
|
|
2
|
+
export { Text } from "./text";
|
|
3
|
+
export { List, ListItem } from "./list";
|
|
4
|
+
export { Blockquote } from "./blockquote";
|
|
5
|
+
export { InlineCode } from "./inline-code";
|
|
6
|
+
export { CodeBlock } from "./code-block";
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
BlockquoteProps,
|
|
10
|
+
CodeBlockProps,
|
|
11
|
+
HeadingLevel,
|
|
12
|
+
HeadingProps,
|
|
13
|
+
InlineCodeProps,
|
|
14
|
+
ListItemProps,
|
|
15
|
+
ListProps,
|
|
16
|
+
TextElement,
|
|
17
|
+
TextProps,
|
|
18
|
+
TypographyTone,
|
|
19
|
+
UnorderedMarker,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
headingLevelVariants,
|
|
24
|
+
orderedListVariants,
|
|
25
|
+
textSizeVariants,
|
|
26
|
+
typographyToneVariants,
|
|
27
|
+
unorderedListMarkerVariants,
|
|
28
|
+
} from "./variants";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
import type { InlineCodeProps } from "./types";
|
|
6
|
+
import { typographyToneVariants } from "./variants";
|
|
7
|
+
|
|
8
|
+
export const InlineCodeBase = (props: InlineCodeProps) => {
|
|
9
|
+
const { tone, className, children, ref, ...rest } = props;
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<code
|
|
13
|
+
ref={ref}
|
|
14
|
+
data-slot="typography-inline-code"
|
|
15
|
+
className={cn(
|
|
16
|
+
typographyToneVariants({ tone }),
|
|
17
|
+
"rounded-md border border-white/10 bg-white/6 px-1.5 py-0.5 font-mono text-[0.925em] font-normal",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...rest}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</code>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
InlineCodeBase.displayName = "InlineCode";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils";
|
|
2
|
+
|
|
3
|
+
import type { ListProps, ListItemProps } from "./types";
|
|
4
|
+
import {
|
|
5
|
+
orderedListVariants,
|
|
6
|
+
typographyToneVariants,
|
|
7
|
+
unorderedListMarkerVariants,
|
|
8
|
+
} from "./variants";
|
|
9
|
+
|
|
10
|
+
export function ListBase(props: ListProps) {
|
|
11
|
+
if ("ordered" in props && props.ordered === true) {
|
|
12
|
+
const {
|
|
13
|
+
tone,
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
ref,
|
|
17
|
+
ordered,
|
|
18
|
+
marker,
|
|
19
|
+
...rest
|
|
20
|
+
} = props;
|
|
21
|
+
|
|
22
|
+
void ordered;
|
|
23
|
+
void marker;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<ol
|
|
27
|
+
ref={ref}
|
|
28
|
+
data-slot="typography-list"
|
|
29
|
+
data-list-type="ordered"
|
|
30
|
+
className={cn(
|
|
31
|
+
typographyToneVariants({ tone }),
|
|
32
|
+
orderedListVariants(),
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...rest}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</ol>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
marker = "disc",
|
|
44
|
+
tone,
|
|
45
|
+
className,
|
|
46
|
+
children,
|
|
47
|
+
ref,
|
|
48
|
+
ordered,
|
|
49
|
+
...rest
|
|
50
|
+
} = props;
|
|
51
|
+
|
|
52
|
+
void ordered;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ul
|
|
56
|
+
ref={ref}
|
|
57
|
+
data-slot="typography-list"
|
|
58
|
+
data-list-type="unordered"
|
|
59
|
+
className={cn(
|
|
60
|
+
typographyToneVariants({ tone }),
|
|
61
|
+
unorderedListMarkerVariants({ marker }),
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
{...rest}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</ul>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ListBase.displayName = "List";
|
|
72
|
+
|
|
73
|
+
export function ListItemBase(props: ListItemProps) {
|
|
74
|
+
const { className, children, ref, ...rest } = props;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<li
|
|
78
|
+
ref={ref}
|
|
79
|
+
data-slot="typography-list-item"
|
|
80
|
+
className={cn("leading-relaxed", className)}
|
|
81
|
+
{...rest}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</li>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ListItemBase.displayName = "ListItem";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ListProps } from "./types";
|
|
2
|
+
|
|
3
|
+
import { ListBase, ListItemBase } from "./list-base";
|
|
4
|
+
|
|
5
|
+
export const ListItem = ListItemBase;
|
|
6
|
+
|
|
7
|
+
function ListRoot(props: ListProps) {
|
|
8
|
+
return <ListBase {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const List = Object.assign(ListRoot, {
|
|
12
|
+
Item: ListItem,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
ListRoot.displayName = "List";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils";
|
|
2
|
+
|
|
3
|
+
import type { TextProps } from "./types";
|
|
4
|
+
import { textSizeVariants, typographyToneVariants } from "./variants";
|
|
5
|
+
|
|
6
|
+
export const TextBase = (props: TextProps) => {
|
|
7
|
+
const {
|
|
8
|
+
as = "p",
|
|
9
|
+
size = "base",
|
|
10
|
+
tone,
|
|
11
|
+
bold,
|
|
12
|
+
italic,
|
|
13
|
+
underline,
|
|
14
|
+
strikethrough,
|
|
15
|
+
highlight,
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
...rest
|
|
19
|
+
} = props;
|
|
20
|
+
|
|
21
|
+
const Component = as;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Component
|
|
25
|
+
data-slot="typography-text"
|
|
26
|
+
className={cn(
|
|
27
|
+
typographyToneVariants({ tone }),
|
|
28
|
+
textSizeVariants({ size }),
|
|
29
|
+
bold && "font-semibold",
|
|
30
|
+
italic && "italic",
|
|
31
|
+
underline && "underline underline-offset-2",
|
|
32
|
+
strikethrough && "line-through",
|
|
33
|
+
highlight && "rounded bg-amber-400/15 px-0.5",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...rest}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</Component>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
TextBase.displayName = "Text";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { VariantProps } from "class-variance-authority";
|
|
2
|
+
import type {
|
|
3
|
+
ComponentProps,
|
|
4
|
+
HTMLAttributes,
|
|
5
|
+
RefObject,
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
textSizeVariants,
|
|
10
|
+
typographyToneVariants,
|
|
11
|
+
unorderedListMarkerVariants,
|
|
12
|
+
} from "./variants";
|
|
13
|
+
|
|
14
|
+
export type TypographyTone = NonNullable<
|
|
15
|
+
VariantProps<typeof typographyToneVariants>["tone"]
|
|
16
|
+
>;
|
|
17
|
+
|
|
18
|
+
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
19
|
+
|
|
20
|
+
export type HeadingProps = Omit<
|
|
21
|
+
ComponentProps<"h1">,
|
|
22
|
+
"color"
|
|
23
|
+
> & {
|
|
24
|
+
level: HeadingLevel;
|
|
25
|
+
/** Visual scale; defaults to `level`. */
|
|
26
|
+
displayLevel?: HeadingLevel;
|
|
27
|
+
tone?: TypographyTone;
|
|
28
|
+
bold?: boolean;
|
|
29
|
+
italic?: boolean;
|
|
30
|
+
underline?: boolean;
|
|
31
|
+
strikethrough?: boolean;
|
|
32
|
+
ref?: RefObject<HTMLHeadingElement>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type TextElement = "p" | "span" | "div" | "label";
|
|
36
|
+
|
|
37
|
+
export type TextProps = Omit<HTMLAttributes<HTMLElement>, "color"> & {
|
|
38
|
+
as?: TextElement;
|
|
39
|
+
size?: NonNullable<VariantProps<typeof textSizeVariants>["size"]>;
|
|
40
|
+
tone?: TypographyTone;
|
|
41
|
+
bold?: boolean;
|
|
42
|
+
italic?: boolean;
|
|
43
|
+
underline?: boolean;
|
|
44
|
+
strikethrough?: boolean;
|
|
45
|
+
highlight?: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type UnorderedMarker = NonNullable<
|
|
49
|
+
VariantProps<typeof unorderedListMarkerVariants>["marker"]
|
|
50
|
+
>;
|
|
51
|
+
|
|
52
|
+
export type ListProps =
|
|
53
|
+
| (Omit<ComponentProps<"ul">, "color"> & {
|
|
54
|
+
ordered?: false;
|
|
55
|
+
marker?: UnorderedMarker;
|
|
56
|
+
tone?: TypographyTone;
|
|
57
|
+
})
|
|
58
|
+
| (Omit<ComponentProps<"ol">, "color"> & {
|
|
59
|
+
ordered: true;
|
|
60
|
+
marker?: undefined;
|
|
61
|
+
tone?: TypographyTone;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export type ListItemProps = ComponentProps<"li"> & {
|
|
65
|
+
ref?: RefObject<HTMLLIElement>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type BlockquoteProps = ComponentProps<"blockquote"> & {
|
|
69
|
+
tone?: TypographyTone;
|
|
70
|
+
/** Attribution label shown in a footer (distinct from the HTML `cite` URL attribute). */
|
|
71
|
+
attribution?: string;
|
|
72
|
+
ref?: RefObject<HTMLQuoteElement>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type InlineCodeProps = Omit<
|
|
76
|
+
ComponentProps<"code">,
|
|
77
|
+
"color"
|
|
78
|
+
> & {
|
|
79
|
+
tone?: TypographyTone;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type CodeBlockProps = Omit<
|
|
83
|
+
ComponentProps<"pre">,
|
|
84
|
+
"color"
|
|
85
|
+
> & {
|
|
86
|
+
tone?: TypographyTone;
|
|
87
|
+
/** Hint for stacked highlighting stacks / aria-labels. */
|
|
88
|
+
language?: string;
|
|
89
|
+
ref?: RefObject<HTMLPreElement>;
|
|
90
|
+
};
|