Added Callout Component

This commit is contained in:
2025-10-05 18:14:52 +02:00
parent b93f846156
commit 0bca3573f7
13 changed files with 375 additions and 17 deletions

View File

@@ -0,0 +1,144 @@
@layer component {
.container {
--callout-bg: var(--color-surface-inverse);
--callout-fg: var(--color-text-inverse);
--callout-symbol: "";
--callout-symbol-color: var(--color-text-inverse);
@mixin my var(--spacing-cozy);
position: relative;
padding: var(--spacing-cozy) var(--spacing-cozy) var(--spacing-cozy) var(--size-12) ;
border: var(--size-1) solid var(--color-surface-inverse);
box-shadow: 2px 2px 0 oklch(from var(--color-surface-inverse) calc(l - 0.075) c h), 4px 4px 0 oklch(from var(--color-surface-inverse) calc(l - 0.2) c h);
&::before {
@mixin pt 0.425em;
content: var(--callout-symbol);
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: flex-start;
justify-content: center;
width: var(--size-8);
height: 100%;
font-size: 1.5em;
color: var(--callout-symbol-color);
background-color: var(--color-surface-inverse);
}
}
.title {
@mixin text-xl;
font-family: var(--font-header);
font-weight: var(--typo-weight-black);
}
.badge {
position: absolute;
top: 0;
right: 0;
padding: 0.2em 0.5em;
font-family: var(--font-mono);
font-size: var(--typo-size-xs);
font-weight: var(--typo-weight-black);
color: var(--callout-fg);
background: var(--callout-bg);
}
.example {
--callout-bg: var(--color-palette-fuchsia);
--callout-symbol: "◆";
}
.info {
--callout-bg: var(--color-state-info);
--callout-symbol: "‽";
}
.warning {
--callout-bg: var(--color-state-warning);
--callout-symbol: "‼";
}
.tip {
--callout-bg: var(--color-palette-lime-green);
--callout-symbol: "★";
}
.spoiler {
& .label {
@mixin text-xl;
font-family: var(--font-header);
font-weight: var(--typo-weight-black);
&::before {
content: "[REDACTED] ";
color: var(--color-state-error);
}
&:hover {
cursor: pointer;
}
}
& .content {
pointer-events: none;
position: relative;
min-height: 3em;
&::after {
content: '████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
font-family: var(--font-mono);
line-height: inherit;
color: var(--color-text-primary);
letter-spacing: -0.15em;
word-break: break-all;
white-space: pre-wrap;
background: var(--color-text-primary);
}
}
& .toggle {
@util hide-visually;
&:checked {
& ~ .label {
&::before {
content: '[REVEALED] ';
color: var(--color-state-success);
}
}
& ~ .content {
&::after {
content: none;
}
}
}
}
}
}

View File

@@ -0,0 +1,32 @@
import React from 'react';
import styles from './Callout.module.css';
interface CalloutProps {
type: 'default' | 'example' | 'info' | 'warning' | 'tip' | 'spoiler';
title?: string;
children: React.ReactNode;
}
export default function Callout({ type, title, children }: CalloutProps) {
const isSpoiler = type === 'spoiler';
const spoilerID = isSpoiler
? `spoiler-${Math.random().toString(36).substr(2, 9)}`
: undefined;
return (
<div className={`${styles.container} ${styles[type]}`}>
{isSpoiler ? (
<>
<input type="checkbox" id={spoilerID} className={styles.toggle} />
<label htmlFor={spoilerID} className={styles.label}>
{title || 'Show spoiler'}
</label>
</>
) : (
<p className={styles.title}>{title}</p>
)}
<span className={styles.badge}>{type.toUpperCase()}</span>
<div className={`${styles.content}`}>{children}</div>
</div>
);
}

View File

@@ -0,0 +1,29 @@
.figure {
position: relative;
border: var(--size-2) solid var(--color-surface-inverse);
clip-path: polygon(0 var(--size-12), var(--size-12) 0, 100% 0, 100% 100%, 0 100%);
}
.caption {
padding: var(--spacing-snug);
font-family: var(--font-mono);
font-size: var(--typo-size-sm);
line-height: var(--typo-leading-relaxed);
color: var(--color-text-inverse);
background: var(--color-surface-inverse);
}
.credit {
display: block;
margin-top: var(--spacing-tight);
font-family: var(--font-mono);
font-size: var(--typo-size-xs);
font-style: normal;
color: var(--color-text-inverse);
text-align: right;
}

View File

@@ -0,0 +1,34 @@
import Image from 'next/image';
import styles from './Figure.module.css';
interface FigureProps {
src: string;
alt: string;
caption?: string;
credit?: string;
}
export default function Figure({ src, alt, caption, credit }: FigureProps) {
const hasCaption = !!caption;
const hasCredit = !!credit;
const showFigcaption = hasCaption || hasCredit;
return (
<figure className={styles.figure}>
<Image
src={src}
alt={alt}
width={800}
height={600}
className={styles.image}
/>
{showFigcaption && (
<figcaption className={styles.caption}>
{hasCaption && <span className={styles.text}>{caption}</span>}
{hasCredit && <cite className={styles.credit}>[{credit}]</cite>}
</figcaption>
)}
</figure>
);
}

View File

@@ -6,6 +6,8 @@ import DefinitionItem from '@/components/Content/DefinitionList/DefinitionItem';
import Accordion from '@/components/Content/Accordion';
import AccordionItem from '@/components/Content/Accordion/AccordionItem';
import Blockquote from '@/components/Content/Blockquote';
import Figure from '@/components/Content/Figure';
import Callout from '@/components/Content/Callout';
const ContentComponents = {
Column,
@@ -16,6 +18,8 @@ const ContentComponents = {
Accordion,
AccordionItem,
Blockquote,
Figure,
Callout,
};
export default ContentComponents;