Add joplin web article (#9)
This commit is contained in:
1 parent
90bf68c4c8
commit
575959ffa2
38 files changed
+1043
-554
No files matched your search
@@ -51,9 +51,6 @@ const MarkdownComponent = memo(function Markdown({ children, className }: Markdo
|
||||
* <Markdown.H1>Title</Markdown.H1>
|
||||
* <Markdown.P>Content goes here.</Markdown.P>
|
||||
* </Markdown>
|
||||
*
|
||||
* // Or with Astro:
|
||||
* <Content components={MARKDOWN_COMPONENTS} />
|
||||
* ```
|
||||
*/
|
||||
export const Markdown = Object.assign(MarkdownComponent, {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { MarkdownUL } from './MarkdownUL/MarkdownUL';
|
||||
import type React from 'react';
|
||||
|
||||
/**
|
||||
* A map of HTML tags to Markdown components for use with MDX or Astro Content.
|
||||
* A map of HTML tags to Markdown components for use with MDX or other Markdown systems.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const MARKDOWN_COMPONENTS: Record<string, React.ComponentType<any>> = {
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('PageHead', () => {
|
||||
it('applies the correct classes for the strategy', () => {
|
||||
render(<PageHead heading="Test" strategy="always-collapsed-fixed" />);
|
||||
const header = screen.getByRole('banner');
|
||||
expect(header).toHaveClass('fixed');
|
||||
expect(header).toHaveClass('sticky');
|
||||
expect(header).toHaveClass('shadow-md');
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn, useScroll, useResizeObserver } from '@ermnvldmr/stl';
|
||||
import React, { type ReactNode, useRef, useEffect, useState } from 'react';
|
||||
import { cn, useScroll } from '@ermnvldmr/stl';
|
||||
import React, { type ReactNode, useEffect, useState } from 'react';
|
||||
|
||||
import { Header } from '../../Header/Header';
|
||||
import { HStack } from '../../HStack/HStack';
|
||||
@@ -68,8 +68,6 @@ export const PageHead = ({
|
||||
}: PageHeadProps) => {
|
||||
const { y: scrollY } = useScroll();
|
||||
const [isCollapsed, setIsCollapsed] = useState(strategy.includes('always-collapsed'));
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const { height } = useResizeObserver(headerRef);
|
||||
|
||||
useEffect(() => {
|
||||
if (strategy === 'collapsible-sticky') {
|
||||
@@ -78,7 +76,7 @@ export const PageHead = ({
|
||||
}, [scrollY, strategy]);
|
||||
|
||||
const rootClasses = cn(
|
||||
'page-head w-full transition-all duration-200 bg-[var(--rb-background)] fixed top-0 z-10 border-b border-[var(--rb-outline)]/10',
|
||||
'page-head w-full transition-all duration-200 bg-[var(--rb-background)] sticky top-0 z-10 border-b border-[var(--rb-outline)]/10',
|
||||
{
|
||||
'shadow-md bg-[var(--rb-background)]/90 backdrop-blur-md': isCollapsed,
|
||||
},
|
||||
@@ -124,28 +122,25 @@ export const PageHead = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header ref={headerRef} className={rootClasses}>
|
||||
<div
|
||||
className={cn(
|
||||
'grid transition-[grid-template-rows] duration-300 ease-in-out',
|
||||
isCollapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]'
|
||||
)}
|
||||
inert={isCollapsed || undefined}
|
||||
>
|
||||
<div className="overflow-hidden">{expandedContent}</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'grid transition-[grid-template-rows] duration-300 ease-in-out',
|
||||
isCollapsed ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'
|
||||
)}
|
||||
inert={!isCollapsed || undefined}
|
||||
>
|
||||
<div className="overflow-hidden">{collapsedContent}</div>
|
||||
</div>
|
||||
</header>
|
||||
<div style={{ height }} />
|
||||
</>
|
||||
<header className={rootClasses}>
|
||||
<div
|
||||
className={cn(
|
||||
'grid transition-[grid-template-rows] duration-300 ease-in-out',
|
||||
isCollapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]'
|
||||
)}
|
||||
inert={isCollapsed || undefined}
|
||||
>
|
||||
<div className="overflow-hidden">{expandedContent}</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'grid transition-[grid-template-rows] duration-300 ease-in-out',
|
||||
isCollapsed ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'
|
||||
)}
|
||||
inert={!isCollapsed || undefined}
|
||||
>
|
||||
<div className="overflow-hidden">{collapsedContent}</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -132,29 +132,49 @@ describe('components/Text', () => {
|
||||
|
||||
// TDD: fails before implementation (old code uses text-[14px], not text-rb-body-m)
|
||||
it('applies text-rb-{type}-{size} class', () => {
|
||||
render(<Text size="m" type="body">Content</Text>);
|
||||
render(
|
||||
<Text size="m" type="body">
|
||||
Content
|
||||
</Text>
|
||||
);
|
||||
expect(screen.getByText('Content')).toHaveClass('text-rb-body-m');
|
||||
});
|
||||
|
||||
it('does not apply legacy px-based font-size classes', () => {
|
||||
render(<Text size="m" type="body">Content</Text>);
|
||||
render(
|
||||
<Text size="m" type="body">
|
||||
Content
|
||||
</Text>
|
||||
);
|
||||
expect(screen.getByText('Content')).not.toHaveClass('text-[14px]');
|
||||
expect(screen.getByText('Content')).not.toHaveClass('leading-[20px]');
|
||||
});
|
||||
|
||||
// Regression guards: pass before and after
|
||||
it('applies font-medium for label type', () => {
|
||||
render(<Text size="m" type="label">Label</Text>);
|
||||
render(
|
||||
<Text size="m" type="label">
|
||||
Label
|
||||
</Text>
|
||||
);
|
||||
expect(screen.getByText('Label')).toHaveClass('font-medium');
|
||||
});
|
||||
|
||||
it('applies font-sans for body type', () => {
|
||||
render(<Text size="m" type="body">Body</Text>);
|
||||
render(
|
||||
<Text size="m" type="body">
|
||||
Body
|
||||
</Text>
|
||||
);
|
||||
expect(screen.getByText('Body')).toHaveClass('font-sans');
|
||||
});
|
||||
|
||||
it('applies font-serif for display type', () => {
|
||||
render(<Text size="l" type="display">Display</Text>);
|
||||
render(
|
||||
<Text size="l" type="display">
|
||||
Display
|
||||
</Text>
|
||||
);
|
||||
expect(screen.getByText('Display')).toHaveClass('font-serif');
|
||||
});
|
||||
});
|
||||
@@ -119,7 +119,19 @@ const TextComponent = forwardRef<HTMLElement, TextProps>(function Text(
|
||||
{...props}
|
||||
ref={internalRef}
|
||||
className={cn(
|
||||
getTypographyClassNames({ type, size, color, bold, italic, underline, strike, align, wrap, overflow, maxLines }),
|
||||
getTypographyClassNames({
|
||||
type,
|
||||
size,
|
||||
color,
|
||||
bold,
|
||||
italic,
|
||||
underline,
|
||||
strike,
|
||||
align,
|
||||
wrap,
|
||||
overflow,
|
||||
maxLines,
|
||||
}),
|
||||
delay !== undefined &&
|
||||
(isVisible
|
||||
? 'animate-in fade-in slide-in-from-bottom-2 duration-1000 ease-out'
|
||||
|
||||
+9
-3
@@ -7,8 +7,12 @@ describe('lib/typography/getTypographyClassNames', () => {
|
||||
});
|
||||
|
||||
it('returns correct class for each type/size combination', () => {
|
||||
expect(getTypographyClassNames({ type: 'display', size: 'l' })).toContain('text-rb-display-l');
|
||||
expect(getTypographyClassNames({ type: 'headline', size: 'm' })).toContain('text-rb-headline-m');
|
||||
expect(getTypographyClassNames({ type: 'display', size: 'l' })).toContain(
|
||||
'text-rb-display-l'
|
||||
);
|
||||
expect(getTypographyClassNames({ type: 'headline', size: 'm' })).toContain(
|
||||
'text-rb-headline-m'
|
||||
);
|
||||
expect(getTypographyClassNames({ type: 'title', size: 's' })).toContain('text-rb-title-s');
|
||||
expect(getTypographyClassNames({ type: 'body', size: 'l' })).toContain('text-rb-body-l');
|
||||
expect(getTypographyClassNames({ type: 'label', size: 'm' })).toContain('text-rb-label-m');
|
||||
@@ -54,7 +58,9 @@ describe('lib/typography/getTypographyClassNames', () => {
|
||||
|
||||
it('applies each color variant', () => {
|
||||
expect(getTypographyClassNames({ color: 'muted' })).toContain('text-[var(--rb-muted-text)]');
|
||||
expect(getTypographyClassNames({ color: 'primary' })).toContain('text-[var(--rb-primary-text)]');
|
||||
expect(getTypographyClassNames({ color: 'primary' })).toContain(
|
||||
'text-[var(--rb-primary-text)]'
|
||||
);
|
||||
expect(getTypographyClassNames({ color: 'error' })).toContain('text-[var(--rb-error-text)]');
|
||||
expect(getTypographyClassNames({ color: 'inherit' })).toContain('text-inherit');
|
||||
});
|
||||
|
||||
@@ -191,29 +191,62 @@
|
||||
|
||||
@theme {
|
||||
/* display */
|
||||
--text-rb-display-l: 3.5625rem; --text-rb-display-l--line-height: 4rem; --text-rb-display-l--letter-spacing: -0.015625rem;
|
||||
--text-rb-display-m: 2.8125rem; --text-rb-display-m--line-height: 3.25rem; --text-rb-display-m--letter-spacing: 0rem;
|
||||
--text-rb-display-s: 2.25rem; --text-rb-display-s--line-height: 2.75rem; --text-rb-display-s--letter-spacing: 0rem;
|
||||
--text-rb-display-l: 3.5625rem;
|
||||
--text-rb-display-l--line-height: 4rem;
|
||||
--text-rb-display-l--letter-spacing: -0.015625rem;
|
||||
--text-rb-display-m: 2.8125rem;
|
||||
--text-rb-display-m--line-height: 3.25rem;
|
||||
--text-rb-display-m--letter-spacing: 0rem;
|
||||
--text-rb-display-s: 2.25rem;
|
||||
--text-rb-display-s--line-height: 2.75rem;
|
||||
--text-rb-display-s--letter-spacing: 0rem;
|
||||
|
||||
/* headline */
|
||||
--text-rb-headline-l: 2rem; --text-rb-headline-l--line-height: 2.5rem; --text-rb-headline-l--letter-spacing: 0rem;
|
||||
--text-rb-headline-m: 1.75rem; --text-rb-headline-m--line-height: 2.25rem; --text-rb-headline-m--letter-spacing: 0rem;
|
||||
--text-rb-headline-s: 1.5rem; --text-rb-headline-s--line-height: 2rem; --text-rb-headline-s--letter-spacing: 0rem;
|
||||
--text-rb-headline-l: 2rem;
|
||||
--text-rb-headline-l--line-height: 2.5rem;
|
||||
--text-rb-headline-l--letter-spacing: 0rem;
|
||||
--text-rb-headline-m: 1.75rem;
|
||||
--text-rb-headline-m--line-height: 2.25rem;
|
||||
--text-rb-headline-m--letter-spacing: 0rem;
|
||||
--text-rb-headline-s: 1.5rem;
|
||||
--text-rb-headline-s--line-height: 2rem;
|
||||
--text-rb-headline-s--letter-spacing: 0rem;
|
||||
|
||||
/* title */
|
||||
--text-rb-title-l: 1.375rem; --text-rb-title-l--line-height: 1.75rem; --text-rb-title-l--letter-spacing: 0rem;
|
||||
--text-rb-title-m: 1rem; --text-rb-title-m--line-height: 1.5rem; --text-rb-title-m--letter-spacing: 0.009375rem;
|
||||
--text-rb-title-s: 0.875rem; --text-rb-title-s--line-height: 1.25rem; --text-rb-title-s--letter-spacing: 0.00625rem;
|
||||
--text-rb-title-l: 1.375rem;
|
||||
--text-rb-title-l--line-height: 1.75rem;
|
||||
--text-rb-title-l--letter-spacing: 0rem;
|
||||
--text-rb-title-m: 1rem;
|
||||
--text-rb-title-m--line-height: 1.5rem;
|
||||
--text-rb-title-m--letter-spacing: 0.009375rem;
|
||||
--text-rb-title-s: 0.875rem;
|
||||
--text-rb-title-s--line-height: 1.25rem;
|
||||
--text-rb-title-s--letter-spacing: 0.00625rem;
|
||||
|
||||
/* body — baseline raised: m = 1rem (was 14px/0.875rem) */
|
||||
--text-rb-body-l: 1.125rem; --text-rb-body-l--line-height: 1.75rem; --text-rb-body-l--letter-spacing: 0.03125rem;
|
||||
--text-rb-body-m: 1rem; --text-rb-body-m--line-height: 1.5rem; --text-rb-body-m--letter-spacing: 0.015625rem;
|
||||
--text-rb-body-s: 0.875rem; --text-rb-body-s--line-height: 1.25rem; --text-rb-body-s--letter-spacing: 0.025rem;
|
||||
--text-rb-body-l: 1.125rem;
|
||||
--text-rb-body-l--line-height: 1.75rem;
|
||||
--text-rb-body-l--letter-spacing: 0.03125rem;
|
||||
--text-rb-body-m: 1rem;
|
||||
--text-rb-body-m--line-height: 1.5rem;
|
||||
--text-rb-body-m--letter-spacing: 0.015625rem;
|
||||
--text-rb-body-s: 0.875rem;
|
||||
--text-rb-body-s--line-height: 1.25rem;
|
||||
--text-rb-body-s--letter-spacing: 0.025rem;
|
||||
|
||||
/* label */
|
||||
--text-rb-label-l: 0.875rem; --text-rb-label-l--line-height: 1.25rem; --text-rb-label-l--letter-spacing: 0.00625rem; --text-rb-label-l--font-weight: 500;
|
||||
--text-rb-label-m: 0.75rem; --text-rb-label-m--line-height: 1rem; --text-rb-label-m--letter-spacing: 0.03125rem; --text-rb-label-m--font-weight: 500;
|
||||
--text-rb-label-s: 0.6875rem; --text-rb-label-s--line-height: 1rem; --text-rb-label-s--letter-spacing: 0.03125rem; --text-rb-label-s--font-weight: 500;
|
||||
--text-rb-label-l: 0.875rem;
|
||||
--text-rb-label-l--line-height: 1.25rem;
|
||||
--text-rb-label-l--letter-spacing: 0.00625rem;
|
||||
--text-rb-label-l--font-weight: 500;
|
||||
--text-rb-label-m: 0.75rem;
|
||||
--text-rb-label-m--line-height: 1rem;
|
||||
--text-rb-label-m--letter-spacing: 0.03125rem;
|
||||
--text-rb-label-m--font-weight: 500;
|
||||
--text-rb-label-s: 0.6875rem;
|
||||
--text-rb-label-s--line-height: 1rem;
|
||||
--text-rb-label-s--letter-spacing: 0.03125rem;
|
||||
--text-rb-label-s--font-weight: 500;
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -285,34 +318,70 @@
|
||||
@layer components {
|
||||
/* ── Scale parents — set CSS vars for all type children below ── */
|
||||
.rb-scale-s {
|
||||
--rb-sz-display: var(--text-rb-display-s); --rb-lh-display: var(--text-rb-display-s--line-height);
|
||||
--rb-sz-headline: var(--text-rb-headline-s); --rb-lh-headline: var(--text-rb-headline-s--line-height);
|
||||
--rb-sz-title: var(--text-rb-title-s); --rb-lh-title: var(--text-rb-title-s--line-height);
|
||||
--rb-sz-body: var(--text-rb-body-s); --rb-lh-body: var(--text-rb-body-s--line-height);
|
||||
--rb-sz-label: var(--text-rb-label-s); --rb-lh-label: var(--text-rb-label-s--line-height);
|
||||
--rb-sz-display: var(--text-rb-display-s);
|
||||
--rb-lh-display: var(--text-rb-display-s--line-height);
|
||||
--rb-sz-headline: var(--text-rb-headline-s);
|
||||
--rb-lh-headline: var(--text-rb-headline-s--line-height);
|
||||
--rb-sz-title: var(--text-rb-title-s);
|
||||
--rb-lh-title: var(--text-rb-title-s--line-height);
|
||||
--rb-sz-body: var(--text-rb-body-s);
|
||||
--rb-lh-body: var(--text-rb-body-s--line-height);
|
||||
--rb-sz-label: var(--text-rb-label-s);
|
||||
--rb-lh-label: var(--text-rb-label-s--line-height);
|
||||
}
|
||||
.rb-scale-m {
|
||||
--rb-sz-display: var(--text-rb-display-m); --rb-lh-display: var(--text-rb-display-m--line-height);
|
||||
--rb-sz-headline: var(--text-rb-headline-m); --rb-lh-headline: var(--text-rb-headline-m--line-height);
|
||||
--rb-sz-title: var(--text-rb-title-m); --rb-lh-title: var(--text-rb-title-m--line-height);
|
||||
--rb-sz-body: var(--text-rb-body-m); --rb-lh-body: var(--text-rb-body-m--line-height);
|
||||
--rb-sz-label: var(--text-rb-label-m); --rb-lh-label: var(--text-rb-label-m--line-height);
|
||||
--rb-sz-display: var(--text-rb-display-m);
|
||||
--rb-lh-display: var(--text-rb-display-m--line-height);
|
||||
--rb-sz-headline: var(--text-rb-headline-m);
|
||||
--rb-lh-headline: var(--text-rb-headline-m--line-height);
|
||||
--rb-sz-title: var(--text-rb-title-m);
|
||||
--rb-lh-title: var(--text-rb-title-m--line-height);
|
||||
--rb-sz-body: var(--text-rb-body-m);
|
||||
--rb-lh-body: var(--text-rb-body-m--line-height);
|
||||
--rb-sz-label: var(--text-rb-label-m);
|
||||
--rb-lh-label: var(--text-rb-label-m--line-height);
|
||||
}
|
||||
.rb-scale-l {
|
||||
--rb-sz-display: var(--text-rb-display-l); --rb-lh-display: var(--text-rb-display-l--line-height);
|
||||
--rb-sz-headline: var(--text-rb-headline-l); --rb-lh-headline: var(--text-rb-headline-l--line-height);
|
||||
--rb-sz-title: var(--text-rb-title-l); --rb-lh-title: var(--text-rb-title-l--line-height);
|
||||
--rb-sz-body: var(--text-rb-body-l); --rb-lh-body: var(--text-rb-body-l--line-height);
|
||||
--rb-sz-label: var(--text-rb-label-l); --rb-lh-label: var(--text-rb-label-l--line-height);
|
||||
--rb-sz-display: var(--text-rb-display-l);
|
||||
--rb-lh-display: var(--text-rb-display-l--line-height);
|
||||
--rb-sz-headline: var(--text-rb-headline-l);
|
||||
--rb-lh-headline: var(--text-rb-headline-l--line-height);
|
||||
--rb-sz-title: var(--text-rb-title-l);
|
||||
--rb-lh-title: var(--text-rb-title-l--line-height);
|
||||
--rb-sz-body: var(--text-rb-body-l);
|
||||
--rb-lh-body: var(--text-rb-body-l--line-height);
|
||||
--rb-sz-label: var(--text-rb-label-l);
|
||||
--rb-lh-label: var(--text-rb-label-l--line-height);
|
||||
}
|
||||
|
||||
/* ── Type children — declare type, read from parent scale vars ── */
|
||||
/* Fallback (second arg to var()) applies m-size when no rb-scale-* parent exists */
|
||||
.rb-text-display { font-family: var(--font-serif); font-size: var(--rb-sz-display, var(--text-rb-display-m)); line-height: var(--rb-lh-display, var(--text-rb-display-m--line-height)); }
|
||||
.rb-text-headline { font-family: var(--font-serif); font-size: var(--rb-sz-headline, var(--text-rb-headline-m)); line-height: var(--rb-lh-headline, var(--text-rb-headline-m--line-height)); }
|
||||
.rb-text-title { font-family: var(--font-sans); font-size: var(--rb-sz-title, var(--text-rb-title-m)); line-height: var(--rb-lh-title, var(--text-rb-title-m--line-height)); }
|
||||
.rb-text-body { font-family: var(--font-sans); font-size: var(--rb-sz-body, var(--text-rb-body-m)); line-height: var(--rb-lh-body, var(--text-rb-body-m--line-height)); }
|
||||
.rb-text-label { font-family: var(--font-sans); font-weight: 500; font-size: var(--rb-sz-label, var(--text-rb-label-m)); line-height: var(--rb-lh-label, var(--text-rb-label-m--line-height)); }
|
||||
.rb-text-display {
|
||||
font-family: var(--font-serif);
|
||||
font-size: var(--rb-sz-display, var(--text-rb-display-m));
|
||||
line-height: var(--rb-lh-display, var(--text-rb-display-m--line-height));
|
||||
}
|
||||
.rb-text-headline {
|
||||
font-family: var(--font-serif);
|
||||
font-size: var(--rb-sz-headline, var(--text-rb-headline-m));
|
||||
line-height: var(--rb-lh-headline, var(--text-rb-headline-m--line-height));
|
||||
}
|
||||
.rb-text-title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--rb-sz-title, var(--text-rb-title-m));
|
||||
line-height: var(--rb-lh-title, var(--text-rb-title-m--line-height));
|
||||
}
|
||||
.rb-text-body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--rb-sz-body, var(--text-rb-body-m));
|
||||
line-height: var(--rb-lh-body, var(--text-rb-body-m--line-height));
|
||||
}
|
||||
.rb-text-label {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
font-size: var(--rb-sz-label, var(--text-rb-label-m));
|
||||
line-height: var(--rb-lh-label, var(--text-rb-label-m--line-height));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
|
||||
Reference in new issue
Block a user