@weasyprint-tsx/ui 0.0.1
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/package.json +7 -0
- package/src/BlockBox.module.css +16 -0
- package/src/BlockBox.tsx +66 -0
- package/src/CodeBlock.tsx +17 -0
- package/src/DotLine.module.css +24 -0
- package/src/DotLine.tsx +45 -0
- package/src/Equation.module.css +4 -0
- package/src/Equation.tsx +38 -0
- package/src/List.module.css +53 -0
- package/src/List.tsx +120 -0
- package/src/Page.module.css +14 -0
- package/src/Page.tsx +33 -0
- package/src/Table.module.css +55 -0
- package/src/Table.tsx +164 -0
- package/src/Title.module.css +75 -0
- package/src/Titles.tsx +105 -0
- package/src/index.ts +9 -0
- package/src/utils.tsx +26 -0
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
margin-left: calc(-1 * var(--block-box-gap, 0));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.block {
|
|
6
|
+
display: inline-block;
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
width: calc(var(--ratio, 1) * 100% / var(--block-box-basis, 1));
|
|
9
|
+
vertical-align: var(--block-box-align, middle);
|
|
10
|
+
padding-left: var(--block-box-gap, 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.centered > * {
|
|
14
|
+
display: block;
|
|
15
|
+
margin: 0 auto;
|
|
16
|
+
}
|
package/src/BlockBox.tsx
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ComponentProps, toChildArray, VNode } from "preact";
|
|
2
|
+
import styles from "./BlockBox.module.css";
|
|
3
|
+
import { joinClasses, mergeStyle } from "./utils";
|
|
4
|
+
|
|
5
|
+
interface BlockBoxProps extends ComponentProps<"div"> {
|
|
6
|
+
children: VNode<BlockProps>[] | VNode<BlockProps>;
|
|
7
|
+
gap?: string;
|
|
8
|
+
basis?: number;
|
|
9
|
+
centered?: boolean;
|
|
10
|
+
align?: "middle" | "top" | "bottom";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface BlockProps extends ComponentProps<"div"> {
|
|
14
|
+
ratio?: number;
|
|
15
|
+
centered?: boolean;
|
|
16
|
+
align?: "middle" | "top" | "bottom";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Block(_props: BlockProps): null {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function BlockBox({
|
|
24
|
+
children,
|
|
25
|
+
className,
|
|
26
|
+
basis,
|
|
27
|
+
gap,
|
|
28
|
+
align,
|
|
29
|
+
centered: parentCentered = true,
|
|
30
|
+
style,
|
|
31
|
+
...props
|
|
32
|
+
}: BlockBoxProps) {
|
|
33
|
+
const blockList = toChildArray(children).filter(
|
|
34
|
+
(child) => (child as VNode).type === Block,
|
|
35
|
+
) as VNode<BlockProps>[];
|
|
36
|
+
|
|
37
|
+
const blockBasis =
|
|
38
|
+
basis ?? blockList.reduce((acc, b) => acc + (b.props.ratio ?? 1), 0);
|
|
39
|
+
|
|
40
|
+
const child = blockList.map(
|
|
41
|
+
({ props: { style, ratio = 1, className, centered, align, ...props } }) => (
|
|
42
|
+
<div
|
|
43
|
+
style={mergeStyle(style, { "--ratio": ratio, "--block-box-align": align })}
|
|
44
|
+
className={joinClasses(
|
|
45
|
+
className,
|
|
46
|
+
styles.block,
|
|
47
|
+
(centered ?? parentCentered) ? styles.centered : undefined,
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
children={child}
|
|
57
|
+
className={joinClasses(className, styles.container)}
|
|
58
|
+
style={mergeStyle(style, {
|
|
59
|
+
"--block-box-basis": blockBasis,
|
|
60
|
+
"--block-box-gap": gap,
|
|
61
|
+
"--block-box-align": align,
|
|
62
|
+
})}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import hljs from "highlight.js";
|
|
2
|
+
import "highlight.js/styles/atom-one-dark.css";
|
|
3
|
+
import { ComponentProps } from "preact";
|
|
4
|
+
|
|
5
|
+
interface CodeBlockProps extends Omit<ComponentProps<"code">, "children"> {
|
|
6
|
+
language: string;
|
|
7
|
+
code: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function CodeBlock({ code, language, ...props }: CodeBlockProps) {
|
|
11
|
+
const codeBlock = hljs.highlight(code, { language: language });
|
|
12
|
+
return (
|
|
13
|
+
<pre style={{breakInside : "avoid"}}>
|
|
14
|
+
<code {...props} dangerouslySetInnerHTML={{ __html: codeBlock.value }} />
|
|
15
|
+
</pre>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* block mode: stacked answer lines */
|
|
2
|
+
.block {
|
|
3
|
+
display: block;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.line {
|
|
7
|
+
display: block;
|
|
8
|
+
min-height: var(--dotline-height, 6mm);
|
|
9
|
+
|
|
10
|
+
margin-bottom: 2mm;
|
|
11
|
+
border-bottom: 1px dotted var(--dotline-color, #808080);
|
|
12
|
+
&:is(:last-child) {
|
|
13
|
+
margin-bottom: 3mm;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* inline mode: fill-in-the-blank inside <p> */
|
|
18
|
+
.inline {
|
|
19
|
+
display: inline-block;
|
|
20
|
+
vertical-align: baseline;
|
|
21
|
+
height: 0;
|
|
22
|
+
margin: 0 1mm;
|
|
23
|
+
border-bottom: 1px dotted var(--dotline-color, #808080);
|
|
24
|
+
}
|
package/src/DotLine.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ComponentProps } from "preact";
|
|
2
|
+
import styles from "./DotLine.module.css";
|
|
3
|
+
import { joinClasses, mergeStyle } from "./utils";
|
|
4
|
+
|
|
5
|
+
interface DotLineProps extends ComponentProps<"span"> {
|
|
6
|
+
num?: number;
|
|
7
|
+
width?: number | string;
|
|
8
|
+
inline?: boolean;
|
|
9
|
+
color?: string;
|
|
10
|
+
lineHeight?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function DotLine({
|
|
14
|
+
num = 1,
|
|
15
|
+
children,
|
|
16
|
+
width = "100%",
|
|
17
|
+
style,
|
|
18
|
+
className = "",
|
|
19
|
+
inline,
|
|
20
|
+
color,
|
|
21
|
+
lineHeight,
|
|
22
|
+
...props
|
|
23
|
+
}: DotLineProps) {
|
|
24
|
+
if (num === 0) return;
|
|
25
|
+
const css = mergeStyle(style, { width, "--dotline-color": color, "--dotline-height": lineHeight });
|
|
26
|
+
|
|
27
|
+
if (inline)
|
|
28
|
+
return (
|
|
29
|
+
<span
|
|
30
|
+
className={joinClasses(className, styles.inline)}
|
|
31
|
+
style={css}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<span className={joinClasses(className, styles.block)} style={css} {...props}>
|
|
38
|
+
{Array.from({ length: num }, (_, k) => (
|
|
39
|
+
<span key={k} className={styles.line}>
|
|
40
|
+
{k === 0 ? children : null}
|
|
41
|
+
</span>
|
|
42
|
+
))}
|
|
43
|
+
</span>
|
|
44
|
+
);
|
|
45
|
+
}
|
package/src/Equation.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { renderToString } from "katex";
|
|
2
|
+
import "katex/contrib/mhchem";
|
|
3
|
+
import "katex/dist/katex.min.css";
|
|
4
|
+
import { ComponentProps } from "preact";
|
|
5
|
+
import styles from "./Equation.module.css";
|
|
6
|
+
import { joinClasses } from "./utils";
|
|
7
|
+
|
|
8
|
+
interface EquationProps extends Omit<ComponentProps<"span">, "children"> {
|
|
9
|
+
tex: string;
|
|
10
|
+
displayMode?: boolean;
|
|
11
|
+
aligned?: boolean;
|
|
12
|
+
chemical?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export function Equation({
|
|
15
|
+
tex,
|
|
16
|
+
displayMode,
|
|
17
|
+
className = "",
|
|
18
|
+
aligned = false,
|
|
19
|
+
chemical = false,
|
|
20
|
+
...props
|
|
21
|
+
}: EquationProps) {
|
|
22
|
+
const alignedCode = aligned
|
|
23
|
+
? `\\begin{aligned}
|
|
24
|
+
${tex}
|
|
25
|
+
\\end{aligned}`
|
|
26
|
+
: tex;
|
|
27
|
+
const chemCode = chemical ? `\\ce{${alignedCode}}` : alignedCode;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<span
|
|
31
|
+
dangerouslySetInnerHTML={{
|
|
32
|
+
__html: renderToString(chemCode, { displayMode }),
|
|
33
|
+
}}
|
|
34
|
+
className={joinClasses(className, styles.equation)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.ol {
|
|
2
|
+
& > .li {
|
|
3
|
+
counter-increment: list-items;
|
|
4
|
+
&::before {
|
|
5
|
+
content: var(--ol-marker-pre, "") counter(list-items, decimal)
|
|
6
|
+
var(--ol-marker-post, ". ");
|
|
7
|
+
margin-right: 1mm;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
&[data-counter="lower-alpha"] .li::before {
|
|
11
|
+
content: var(--ol-marker-pre, "") counter(list-items, lower-alpha)
|
|
12
|
+
var(--ol-marker-post, ". ");
|
|
13
|
+
}
|
|
14
|
+
&[data-counter="upper-alpha"] .li::before {
|
|
15
|
+
content: var(--ol-marker-pre, "") counter(list-items, upper-alpha)
|
|
16
|
+
var(--ol-marker-post, ". ");
|
|
17
|
+
}
|
|
18
|
+
&[data-counter="lower-roman"] .li::before {
|
|
19
|
+
content: var(--ol-marker-pre, "") counter(list-items, lower-roman)
|
|
20
|
+
var(--ol-marker-post, ". ");
|
|
21
|
+
}
|
|
22
|
+
&[data-counter="upper-roman"] .li::before {
|
|
23
|
+
content: var(--ol-marker-pre, "") counter(list-items, upper-roman)
|
|
24
|
+
var(--ol-marker-post, ". ");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.ul {
|
|
29
|
+
.li {
|
|
30
|
+
&::before {
|
|
31
|
+
content: var(--ul-marker, "•");
|
|
32
|
+
margin-right: 1mm;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* hanging indent so wrapped text aligns under content, not marker */
|
|
38
|
+
.li {
|
|
39
|
+
&:not(:last-child) {
|
|
40
|
+
margin-bottom: var(--list-gap, 0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* pre+body side-by-side on an inner wrapper — outer div stays unstyled */
|
|
45
|
+
.preWrapper {
|
|
46
|
+
display: grid;
|
|
47
|
+
grid-template-columns: max-content 1fr;
|
|
48
|
+
align-items: start;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.pre {
|
|
52
|
+
padding-right: 1mm;
|
|
53
|
+
}
|
package/src/List.tsx
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { ComponentChildren, ComponentProps } from "preact";
|
|
2
|
+
import styles from "./List.module.css";
|
|
3
|
+
import { cssString, joinClasses, mergeStyle } from "./utils";
|
|
4
|
+
|
|
5
|
+
export type CounterType =
|
|
6
|
+
| "decimal"
|
|
7
|
+
| "lower-alpha"
|
|
8
|
+
| "upper-alpha"
|
|
9
|
+
| "lower-roman"
|
|
10
|
+
| "upper-roman";
|
|
11
|
+
|
|
12
|
+
interface ListItemProps extends ComponentProps<"div"> {
|
|
13
|
+
count?: number;
|
|
14
|
+
marker?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ListProps extends ComponentProps<"div"> {
|
|
18
|
+
pre?: ComponentChildren;
|
|
19
|
+
gap?: string | number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface OLProps extends ListProps {
|
|
23
|
+
start?: number;
|
|
24
|
+
counterType?: CounterType;
|
|
25
|
+
markerPre?: string;
|
|
26
|
+
markerPost?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ULProps extends ListProps {
|
|
30
|
+
marker?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function LI({
|
|
34
|
+
className = "",
|
|
35
|
+
count = NaN,
|
|
36
|
+
style,
|
|
37
|
+
marker,
|
|
38
|
+
...props
|
|
39
|
+
}: ListItemProps) {
|
|
40
|
+
const css = mergeStyle(style, {
|
|
41
|
+
counterReset: count ? `list-items ${count - 1}` : undefined,
|
|
42
|
+
"--ul-marker": cssString(marker),
|
|
43
|
+
});
|
|
44
|
+
return (
|
|
45
|
+
<div style={css} {...props} className={joinClasses(className, styles.li)} />
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function Body({
|
|
50
|
+
pre,
|
|
51
|
+
children,
|
|
52
|
+
}: {
|
|
53
|
+
pre?: ComponentChildren;
|
|
54
|
+
children: ComponentChildren;
|
|
55
|
+
}) {
|
|
56
|
+
if (!pre) return <>{children}</>;
|
|
57
|
+
return (
|
|
58
|
+
<div className={styles.preWrapper}>
|
|
59
|
+
<div className={styles.pre}>{pre}</div>
|
|
60
|
+
<div>{children}</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function OL({
|
|
66
|
+
className = "",
|
|
67
|
+
start = 1,
|
|
68
|
+
counterType = "decimal",
|
|
69
|
+
markerPre,
|
|
70
|
+
markerPost,
|
|
71
|
+
style,
|
|
72
|
+
pre,
|
|
73
|
+
gap,
|
|
74
|
+
children,
|
|
75
|
+
...props
|
|
76
|
+
}: OLProps) {
|
|
77
|
+
const css = mergeStyle(style, {
|
|
78
|
+
counterReset: `list-items ${start - 1}`,
|
|
79
|
+
"--ol-marker-pre": cssString(markerPre),
|
|
80
|
+
"--ol-marker-post": cssString(markerPost),
|
|
81
|
+
"--list-gap": gap,
|
|
82
|
+
});
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
{...props}
|
|
86
|
+
className={joinClasses(className, styles.ol)}
|
|
87
|
+
style={css}
|
|
88
|
+
data-counter={counterType}
|
|
89
|
+
>
|
|
90
|
+
<Body pre={pre}>{children}</Body>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function UL({
|
|
96
|
+
className = "",
|
|
97
|
+
marker,
|
|
98
|
+
style,
|
|
99
|
+
children,
|
|
100
|
+
pre,
|
|
101
|
+
gap,
|
|
102
|
+
...props
|
|
103
|
+
}: ULProps) {
|
|
104
|
+
const css = mergeStyle(style, {
|
|
105
|
+
"--ul-marker": cssString(marker),
|
|
106
|
+
"--list-gap": gap,
|
|
107
|
+
});
|
|
108
|
+
return (
|
|
109
|
+
<div {...props} className={joinClasses(className, styles.ul)} style={css}>
|
|
110
|
+
<Body pre={pre}>{children}</Body>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function List({
|
|
116
|
+
kind,
|
|
117
|
+
...props
|
|
118
|
+
}: ListProps & { kind: "ul" | "ol"; start?: number }) {
|
|
119
|
+
return kind === "ul" ? <UL {...props} /> : <OL {...props} />;
|
|
120
|
+
}
|
package/src/Page.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ComponentProps } from "preact";
|
|
2
|
+
import styles from "./Page.module.css";
|
|
3
|
+
import { joinClasses, mergeStyle } from "./utils";
|
|
4
|
+
interface PageProps {
|
|
5
|
+
page?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function PageBreak({ page }: PageProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
style={{
|
|
12
|
+
page,
|
|
13
|
+
}}
|
|
14
|
+
className={styles.pagebreak}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
export function Page({
|
|
19
|
+
page,
|
|
20
|
+
style,
|
|
21
|
+
className = "",
|
|
22
|
+
...props
|
|
23
|
+
}: PageProps & ComponentProps<"section">) {
|
|
24
|
+
const css = mergeStyle(style, { page });
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<section
|
|
28
|
+
style={css}
|
|
29
|
+
className={joinClasses(className, styles.page)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.table {
|
|
2
|
+
--table-border-width: 1px;
|
|
3
|
+
--table-border-color: currentColor;
|
|
4
|
+
border-collapse: collapse;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.table th,
|
|
8
|
+
.table td {
|
|
9
|
+
text-align: center;
|
|
10
|
+
vertical-align: middle;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.table th {
|
|
14
|
+
background-color: var(--table-header-color, transparent);
|
|
15
|
+
font-size: var(--table-header-fontsize, inherit);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.table td {
|
|
19
|
+
background-color: var(--table-cell-color, transparent);
|
|
20
|
+
font-size: var(--table-cell-fontsize, inherit);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ── Row orientation ──────────────────────────────────────────── */
|
|
24
|
+
.row td,
|
|
25
|
+
.row th {
|
|
26
|
+
border-right: var(--table-border-width, 1px) solid var(--table-border-color, currentColor);
|
|
27
|
+
border-bottom: var(--table-border-width, 1px) solid var(--table-border-color, currentColor);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.row tr td:last-child,
|
|
31
|
+
.row tr th:last-child {
|
|
32
|
+
border-right: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.row tr:last-child td,
|
|
36
|
+
.row tr:last-child th {
|
|
37
|
+
border-bottom: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ── Col orientation ──────────────────────────────────────────── */
|
|
41
|
+
.col td,
|
|
42
|
+
.col th {
|
|
43
|
+
border-right: var(--table-border-width, 1px) solid var(--table-border-color, currentColor);
|
|
44
|
+
border-bottom: var(--table-border-width, 1px) solid var(--table-border-color, currentColor);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.col tr td:last-child,
|
|
48
|
+
.col tr th:last-child {
|
|
49
|
+
border-right: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.col tbody tr:last-child td,
|
|
53
|
+
.col tbody tr:last-child th {
|
|
54
|
+
border-bottom: none;
|
|
55
|
+
}
|
package/src/Table.tsx
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentChild,
|
|
3
|
+
ComponentProps,
|
|
4
|
+
toChildArray,
|
|
5
|
+
VNode
|
|
6
|
+
} from "preact";
|
|
7
|
+
import styles from "./Table.module.css";
|
|
8
|
+
import { joinClasses, mergeStyle } from "./utils";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export interface TableEntryProps extends ComponentProps<"th"> {
|
|
13
|
+
content: ComponentChild[];
|
|
14
|
+
contentClass?: string;
|
|
15
|
+
headerBg?: string;
|
|
16
|
+
cellBg?: string;
|
|
17
|
+
headerFontSize?: string;
|
|
18
|
+
cellFontSize?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Entry(_props: TableEntryProps): null {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TableProps extends ComponentProps<"table"> {
|
|
26
|
+
orientation?: "col" | "row";
|
|
27
|
+
contentClass?: string;
|
|
28
|
+
headerClass?: string;
|
|
29
|
+
headerBg?: string;
|
|
30
|
+
cellBg?: string;
|
|
31
|
+
headerFontSize?: string;
|
|
32
|
+
cellFontSize?: string;
|
|
33
|
+
borderWidth?: string;
|
|
34
|
+
borderColor?: string;
|
|
35
|
+
children: VNode<TableEntryProps> | VNode<TableEntryProps>[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export function Table({
|
|
40
|
+
children,
|
|
41
|
+
orientation = "col",
|
|
42
|
+
className,
|
|
43
|
+
style,
|
|
44
|
+
contentClass: parentContentClass = "",
|
|
45
|
+
headerClass,
|
|
46
|
+
headerBg,
|
|
47
|
+
cellBg,
|
|
48
|
+
headerFontSize,
|
|
49
|
+
cellFontSize,
|
|
50
|
+
borderWidth,
|
|
51
|
+
borderColor,
|
|
52
|
+
...props
|
|
53
|
+
}: TableProps) {
|
|
54
|
+
const headers = toChildArray(children)
|
|
55
|
+
.filter((child) => (child as VNode).type === Entry)
|
|
56
|
+
.map((child) => (child as VNode<TableEntryProps>).props);
|
|
57
|
+
|
|
58
|
+
const tableClass = joinClasses(
|
|
59
|
+
className,
|
|
60
|
+
styles.table,
|
|
61
|
+
styles[orientation],
|
|
62
|
+
).trim();
|
|
63
|
+
|
|
64
|
+
const tableStyle = mergeStyle(style, {
|
|
65
|
+
"--table-header-color": headerBg,
|
|
66
|
+
"--table-cell-color": cellBg,
|
|
67
|
+
"--table-header-fontsize": headerFontSize,
|
|
68
|
+
"--table-cell-fontsize": cellFontSize,
|
|
69
|
+
"--table-border-width": borderWidth,
|
|
70
|
+
"--table-border-color": borderColor,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (orientation === "row") {
|
|
74
|
+
return (
|
|
75
|
+
<table className={tableClass} style={tableStyle} {...props}>
|
|
76
|
+
<tbody>
|
|
77
|
+
{headers.map(
|
|
78
|
+
({
|
|
79
|
+
children,
|
|
80
|
+
content,
|
|
81
|
+
className,
|
|
82
|
+
contentClass,
|
|
83
|
+
style,
|
|
84
|
+
headerBg,
|
|
85
|
+
cellBg,
|
|
86
|
+
headerFontSize,
|
|
87
|
+
cellFontSize,
|
|
88
|
+
...props
|
|
89
|
+
}, i) => (
|
|
90
|
+
<tr key={i}>
|
|
91
|
+
<th
|
|
92
|
+
scope="row"
|
|
93
|
+
className={joinClasses(className, headerClass)}
|
|
94
|
+
style={mergeStyle(style, {
|
|
95
|
+
"--table-header-color": headerBg,
|
|
96
|
+
"--table-header-fontsize": headerFontSize,
|
|
97
|
+
})}
|
|
98
|
+
{...props}
|
|
99
|
+
>
|
|
100
|
+
{children}
|
|
101
|
+
</th>
|
|
102
|
+
{content.map((cell, j) => (
|
|
103
|
+
<td
|
|
104
|
+
key={j}
|
|
105
|
+
className={joinClasses(contentClass, parentContentClass)}
|
|
106
|
+
style={{ "--table-cell-color": cellBg, "--table-cell-fontsize": cellFontSize }}
|
|
107
|
+
>
|
|
108
|
+
{cell}
|
|
109
|
+
</td>
|
|
110
|
+
))}
|
|
111
|
+
</tr>
|
|
112
|
+
),
|
|
113
|
+
)}
|
|
114
|
+
</tbody>
|
|
115
|
+
</table>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const rowCount = Math.max(...headers.map((h) => h.content.length));
|
|
120
|
+
return (
|
|
121
|
+
<table className={tableClass} style={tableStyle} {...props}>
|
|
122
|
+
<thead>
|
|
123
|
+
<tr>
|
|
124
|
+
{headers.map(
|
|
125
|
+
(
|
|
126
|
+
{ children, content, className, contentClass, style, headerBg, cellBg, headerFontSize, cellFontSize, ...props },
|
|
127
|
+
i,
|
|
128
|
+
) => (
|
|
129
|
+
<th
|
|
130
|
+
key={i}
|
|
131
|
+
className={joinClasses(className, headerClass)}
|
|
132
|
+
style={mergeStyle(style, {
|
|
133
|
+
"--table-header-color": headerBg,
|
|
134
|
+
"--table-header-fontsize": headerFontSize,
|
|
135
|
+
})}
|
|
136
|
+
{...props}
|
|
137
|
+
>
|
|
138
|
+
{children}
|
|
139
|
+
</th>
|
|
140
|
+
),
|
|
141
|
+
)}
|
|
142
|
+
</tr>
|
|
143
|
+
</thead>
|
|
144
|
+
<tbody>
|
|
145
|
+
{Array.from({ length: rowCount }, (_, rowIdx) => (
|
|
146
|
+
<tr key={rowIdx}>
|
|
147
|
+
{headers.map(
|
|
148
|
+
({ content, children, contentClass = "", cellBg, cellFontSize, ...props }, colIdx) => (
|
|
149
|
+
<td
|
|
150
|
+
key={colIdx}
|
|
151
|
+
{...props}
|
|
152
|
+
className={joinClasses(contentClass, parentContentClass)}
|
|
153
|
+
style={{ "--table-cell-color": cellBg, "--table-cell-fontsize": cellFontSize }}
|
|
154
|
+
>
|
|
155
|
+
{content[rowIdx]}
|
|
156
|
+
</td>
|
|
157
|
+
),
|
|
158
|
+
)}
|
|
159
|
+
</tr>
|
|
160
|
+
))}
|
|
161
|
+
</tbody>
|
|
162
|
+
</table>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
.h1,
|
|
3
|
+
.h2,
|
|
4
|
+
.h3,
|
|
5
|
+
.h4,
|
|
6
|
+
.h5,
|
|
7
|
+
.h6 {
|
|
8
|
+
&::before {
|
|
9
|
+
float: left;
|
|
10
|
+
clear: left;
|
|
11
|
+
}
|
|
12
|
+
&[data-marker]::before {
|
|
13
|
+
content: attr(data-marker);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
.h1 {
|
|
19
|
+
color: var(--h1-color, inherit);
|
|
20
|
+
font-size: var(--h1-fontsize, inherit);
|
|
21
|
+
counter-reset: count-h2 count-h3 count-h4 count-h5 count-h6;
|
|
22
|
+
counter-increment: count-h1;
|
|
23
|
+
&::before {
|
|
24
|
+
content: counter(count-h1, upper-roman) ". ";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.h2 {
|
|
29
|
+
color: var(--h2-color, inherit);
|
|
30
|
+
font-size: var(--h2-fontsize, inherit);
|
|
31
|
+
counter-reset: count-h3 count-h4 count-h5 count-h6;
|
|
32
|
+
counter-increment: count-h2;
|
|
33
|
+
&::before {
|
|
34
|
+
content: counter(count-h2, decimal) ". ";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.h3 {
|
|
39
|
+
color: var(--h3-color, inherit);
|
|
40
|
+
font-size: var(--h3-fontsize, inherit);
|
|
41
|
+
counter-reset: count-h4 count-h5 count-h6;
|
|
42
|
+
counter-increment: count-h3;
|
|
43
|
+
&::before {
|
|
44
|
+
content: counter(count-h3, lower-alpha) ". ";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.h4 {
|
|
49
|
+
color: var(--h4-color, inherit);
|
|
50
|
+
font-size: var(--h4-fontsize, inherit);
|
|
51
|
+
counter-reset: count-h5 count-h6;
|
|
52
|
+
counter-increment: count-h4;
|
|
53
|
+
&::before {
|
|
54
|
+
content: counter(count-h4, lower-alpha) ". ";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.h5 {
|
|
59
|
+
color: var(--h5-color, inherit);
|
|
60
|
+
font-size: var(--h5-fontsize, inherit);
|
|
61
|
+
counter-reset: count-h6;
|
|
62
|
+
counter-increment: count-h5;
|
|
63
|
+
&::before {
|
|
64
|
+
content: counter(count-h5, lower-alpha) ". ";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.h6 {
|
|
69
|
+
color: var(--h6-color, inherit);
|
|
70
|
+
font-size: var(--h6-fontsize, inherit);
|
|
71
|
+
counter-increment: count-h6;
|
|
72
|
+
&::before {
|
|
73
|
+
content: counter(count-h6, lower-alpha) ". ";
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/Titles.tsx
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ComponentProps, ComponentType } from "preact";
|
|
2
|
+
import styles from "./Title.module.css";
|
|
3
|
+
import { joinClasses, mergeStyle } from "./utils";
|
|
4
|
+
|
|
5
|
+
type Htype = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
|
6
|
+
|
|
7
|
+
type Hprops<h extends Htype> = ComponentProps<h> & {
|
|
8
|
+
marker?: string;
|
|
9
|
+
color?: string;
|
|
10
|
+
fontSize?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function H1({ className = "", marker, color, fontSize, style, ...props }: Hprops<"h1">) {
|
|
14
|
+
return (
|
|
15
|
+
<h1
|
|
16
|
+
data-marker={marker}
|
|
17
|
+
className={joinClasses(className, styles.h1)}
|
|
18
|
+
style={mergeStyle(style, { "--h1-color": color, "--h1-fontsize": fontSize })}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
export function H2({ className = "", marker, color, fontSize, style, ...props }: Hprops<"h2">) {
|
|
24
|
+
return (
|
|
25
|
+
<h2
|
|
26
|
+
data-marker={marker}
|
|
27
|
+
className={joinClasses(className, styles.h2)}
|
|
28
|
+
style={mergeStyle(style, { "--h2-color": color, "--h2-fontsize": fontSize })}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
export function H3({ className = "", marker, color, fontSize, style, ...props }: Hprops<"h3">) {
|
|
34
|
+
return (
|
|
35
|
+
<h3
|
|
36
|
+
data-marker={marker}
|
|
37
|
+
className={joinClasses(className, styles.h3)}
|
|
38
|
+
style={mergeStyle(style, { "--h3-color": color, "--h3-fontsize": fontSize })}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
export function H4({ className = "", marker, color, fontSize, style, ...props }: Hprops<"h4">) {
|
|
44
|
+
return (
|
|
45
|
+
<h4
|
|
46
|
+
data-marker={marker}
|
|
47
|
+
className={joinClasses(className, styles.h4)}
|
|
48
|
+
style={mergeStyle(style, { "--h4-color": color, "--h4-fontsize": fontSize })}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
export function H5({ className = "", marker, color, fontSize, style, ...props }: Hprops<"h5">) {
|
|
54
|
+
return (
|
|
55
|
+
<h5
|
|
56
|
+
data-marker={marker}
|
|
57
|
+
className={joinClasses(className, styles.h5)}
|
|
58
|
+
style={mergeStyle(style, { "--h5-color": color, "--h5-fontsize": fontSize })}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
export function H6({ className = "", marker, color, fontSize, style, ...props }: Hprops<"h6">) {
|
|
64
|
+
return (
|
|
65
|
+
<h6
|
|
66
|
+
data-marker={marker}
|
|
67
|
+
className={joinClasses(className, styles.h6)}
|
|
68
|
+
style={mergeStyle(style, { "--h6-color": color, "--h6-fontsize": fontSize })}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const TAG_MAP = { h1: H1, h2: H2, h3: H3, h4: H4, h5: H5, h6: H6 };
|
|
75
|
+
|
|
76
|
+
type TitleProps<h extends Htype> = Hprops<h> & { type: h };
|
|
77
|
+
export function Title<h extends Htype>({ type, ...props }: TitleProps<h>) {
|
|
78
|
+
const Tag = TAG_MAP[type] as ComponentType<Hprops<h>>;
|
|
79
|
+
return <Tag {...(props as Hprops<h>)} />;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function ResetCounter({
|
|
83
|
+
type,
|
|
84
|
+
value,
|
|
85
|
+
}: {
|
|
86
|
+
type: Htype | Htype[];
|
|
87
|
+
value?: number | (number | null)[];
|
|
88
|
+
}) {
|
|
89
|
+
const types = Array.isArray(type) ? type : [type];
|
|
90
|
+
const values = Array.isArray(value) ? value : [value];
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
style={{
|
|
94
|
+
counterReset: types
|
|
95
|
+
.map((t, k) =>
|
|
96
|
+
`count-${t} ${(values[k % types.length] ?? 1) - 1}`.trim(),
|
|
97
|
+
)
|
|
98
|
+
.join(" "),
|
|
99
|
+
visibility: "hidden",
|
|
100
|
+
height: 0,
|
|
101
|
+
width: 0,
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Block, BlockBox } from "./BlockBox";
|
|
2
|
+
export { CodeBlock } from "./CodeBlock";
|
|
3
|
+
export { DotLine } from "./DotLine";
|
|
4
|
+
export { Equation } from "./Equation";
|
|
5
|
+
export { LI, OL, UL } from "./List";
|
|
6
|
+
export { Page, PageBreak } from "./Page";
|
|
7
|
+
export { Entry, Table } from "./Table";
|
|
8
|
+
export { H1, H2, H3, H4, H5, H6, ResetCounter, Title } from "./Titles";
|
|
9
|
+
|
package/src/utils.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CSSProperties, SignalLike, Signalish } from "preact";
|
|
2
|
+
|
|
3
|
+
export const cssString = (v: string | undefined) =>
|
|
4
|
+
v !== undefined ? `"${v}"` : undefined;
|
|
5
|
+
|
|
6
|
+
export function joinClasses(
|
|
7
|
+
...classes: (string | SignalLike<string | undefined> | undefined)[]
|
|
8
|
+
): string {
|
|
9
|
+
return classes.join(" ").trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function mergeStyle(
|
|
13
|
+
base: Signalish<string | CSSProperties | undefined>,
|
|
14
|
+
extra: CSSProperties,
|
|
15
|
+
): CSSProperties | string {
|
|
16
|
+
if (typeof base === "string") {
|
|
17
|
+
const extraStr = Object.entries(extra)
|
|
18
|
+
.filter(([, v]) => v != null)
|
|
19
|
+
.map(
|
|
20
|
+
([k, v]) => `${k.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}:${v}`,
|
|
21
|
+
)
|
|
22
|
+
.join(";");
|
|
23
|
+
return extraStr ? `${base};${extraStr}` : base;
|
|
24
|
+
}
|
|
25
|
+
return { ...(base?.value as CSSProperties), ...extra };
|
|
26
|
+
}
|