Skip to content

Commit dc47176

Browse files
feat: add 'highlight' component
This component shows a box with a title. Convenient for theorems and such.
1 parent 99b1c2f commit dc47176

File tree

4 files changed

+110
-3
lines changed

4 files changed

+110
-3
lines changed

browser/css/tw-config.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { SlydeHtmlDocumentCssProperties } from '#browser/css/types';
22

3+
/** Tells the config to force this to scale only using `--unit`. */
4+
const scaleWithUnit = Object.fromEntries(
5+
Array.from({ length: 1000 }, (_ignore, amount) => [amount, `calc(${amount} * var(--unit))`])
6+
);
7+
38
/** The config for tailwind in the browser. */
49
export const tailwindConfig = function tailwindConfig({
510
background,
@@ -9,6 +14,12 @@ export const tailwindConfig = function tailwindConfig({
914
}: SlydeHtmlDocumentCssProperties): { theme: Record<string, Record<string, unknown>> } {
1015
return {
1116
theme: {
17+
/**
18+
* Allow **only** the scale of `--unit` which is the unit of measurement in a slyde HTML document as it changes
19+
* based on the size of the document. All other measurements don't.
20+
*/
21+
borderWidth: scaleWithUnit,
22+
1223
/**
1324
* Allow **only** these colors.
1425
*/
@@ -37,9 +48,7 @@ export const tailwindConfig = function tailwindConfig({
3748
* Allow **only** the scale of `--unit` which is the unit of measurement in a slyde HTML document as it changes
3849
* based on the size of the document. All other measurements don't.
3950
*/
40-
spacing: Object.fromEntries(
41-
Array.from({ length: 1000 }, (_ignore, amount) => [amount, `calc(${amount} * var(--unit))`])
42-
),
51+
spacing: scaleWithUnit,
4352
},
4453
};
4554
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Component } from '#lib/core/components/class';
2+
3+
/**
4+
* A component that just shows a highlight block with a thicker left border.
5+
*
6+
* **Example**:
7+
* ```Text
8+
* ┌──────────────────────────────────────────────┐
9+
* ┃ Some text to highlight │
10+
* └──────────────────────────────────────────────┘
11+
* ```
12+
*/
13+
@Component.register.using({ plugin: false })
14+
export class Highlight extends Component {
15+
/** The color of the border around the box. */
16+
readonly #borderColor: string = Component.utils.extract({
17+
aliases: ['border-color', 'color'],
18+
context: this,
19+
fallback: 'var(--primary)',
20+
});
21+
22+
/** The color of the border around the box. */
23+
readonly #title: string | null = Component.utils.extract({
24+
aliases: ['title', 'header'],
25+
context: this,
26+
fallback: null,
27+
});
28+
29+
@Component.utils.children.require
30+
// eslint-disable-next-line jsdoc/require-jsdoc
31+
public render({ children }: Component.RenderArguments): string {
32+
// eslint-disable-next-line no-inline-comments
33+
let title = /*HTML*/ `
34+
<h4 class="flex items-center gap-2 text-LG font-semibold" style="color:${this.#borderColor}">
35+
${this.#title}
36+
</h4>
37+
`;
38+
39+
if (this.#title === null) title = '';
40+
41+
const borderColor = `border-color:${this.#borderColor}`;
42+
const borderWidth = `border-width:calc((1/6)*var(--unit));border-left-width:calc((3/6)*var(--unit));`;
43+
44+
// eslint-disable-next-line no-inline-comments
45+
return /*HTML*/ `
46+
<div class="p-1 mt-1" style="${borderColor};${borderWidth}">
47+
${title}
48+
<div id="${this.id}-children-container">
49+
${children?.()}
50+
</div>
51+
</div>`;
52+
}
53+
54+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this, jsdoc/require-jsdoc
55+
public hierarchy(): ReturnType<Component.Interface['hierarchy']> {
56+
return [Component.level.block, '+'];
57+
}
58+
}

lib/core/components/blocks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from '#lib/core/components/blocks/highlight';
12
export * from '#lib/core/components/blocks/image';
23
export * from '#lib/core/components/blocks/point';
34
export * from '#lib/core/components/blocks/qr-core';
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, expect, test } from 'vitest';
2+
import { Component } from '#lib/core/components/class';
3+
import { Highlight } from '#lib/core/components/blocks/highlight';
4+
5+
const Class = Highlight;
6+
7+
describe(`class ${Class.name} implements Component`, () => {
8+
test('Is registered as a component', () => {
9+
expect(Component.retrieve(Class.name)).toBe(Class);
10+
});
11+
12+
const construct = {
13+
attributes: {},
14+
focusMode: 'default' as const,
15+
id: '',
16+
level: Component.level.block,
17+
path: '//',
18+
} satisfies Component.ConstructorArguments;
19+
20+
test('Is creatable', () => {
21+
expect(() => new Class({ ...construct })).not.toThrow();
22+
});
23+
24+
const pattern = '<VERY-SPECIFIC-PATTERN>' as const;
25+
const children = (() => pattern) as () => string;
26+
const render = {} satisfies Component.RenderArguments;
27+
28+
test('renders with children', () => {
29+
expect(() => new Class({ ...construct }).render({ ...render, children })).not.toThrow();
30+
});
31+
32+
test('does not render without children', () => {
33+
expect(() => new Class({ ...construct }).render({ ...render })).toThrow();
34+
});
35+
36+
test('uses children within somewhere', () => {
37+
expect(new Class({ ...construct }).render({ ...render, children })).toContain(pattern);
38+
});
39+
});

0 commit comments

Comments
 (0)