Added Callout Component
This commit is contained in:
144
src/components/Content/Callout/Callout.module.css
Normal file
144
src/components/Content/Callout/Callout.module.css
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/components/Content/Callout/index.tsx
Normal file
32
src/components/Content/Callout/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { textQuoteIcon } from '@keystar/ui/icon/icons/textQuoteIcon';
|
||||
import { block } from '@keystatic/core/content-components';
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
const blockquoteComponents = {
|
||||
const blockquoteComponent = {
|
||||
Blockquote: block({
|
||||
label: 'Blockquote',
|
||||
icon: textQuoteIcon,
|
||||
@@ -31,4 +31,4 @@ const blockquoteComponents = {
|
||||
}),
|
||||
};
|
||||
|
||||
export default blockquoteComponents;
|
||||
export default blockquoteComponent;
|
||||
|
||||
47
src/keystatic/components/general/callout.ts
Normal file
47
src/keystatic/components/general/callout.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { wrapper } from '@keystatic/core/content-components';
|
||||
import { fields } from '@keystatic/core';
|
||||
import { megaphoneIcon } from '@keystar/ui/icon/icons/megaphoneIcon';
|
||||
|
||||
const calloutComponent = {
|
||||
Callout: wrapper({
|
||||
label: 'Callout',
|
||||
icon: megaphoneIcon,
|
||||
schema: {
|
||||
type: fields.select({
|
||||
label: 'Type',
|
||||
defaultValue: 'default',
|
||||
options: [
|
||||
{
|
||||
label: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
label: 'Example',
|
||||
value: 'example',
|
||||
},
|
||||
{
|
||||
label: 'Info',
|
||||
value: 'info',
|
||||
},
|
||||
{
|
||||
label: 'Warning',
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
label: 'Tip',
|
||||
value: 'tip',
|
||||
},
|
||||
{
|
||||
label: 'Spoiler',
|
||||
value: 'spoiler',
|
||||
},
|
||||
],
|
||||
}),
|
||||
title: fields.text({
|
||||
label: 'Title',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default calloutComponent;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { imageIcon } from '@keystar/ui/icon/icons/imageIcon';
|
||||
import { block } from '@keystatic/core/content-components';
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
const figureComponent = {
|
||||
Figure: block({
|
||||
label: 'Figure',
|
||||
icon: imageIcon,
|
||||
schema: {
|
||||
src: fields.image({
|
||||
label: 'Image',
|
||||
directory: 'public/images/figures',
|
||||
publicPath: '/images/figures',
|
||||
}),
|
||||
alt: fields.text({
|
||||
label: 'Alt Text',
|
||||
validation: {
|
||||
length: {
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
caption: fields.text({
|
||||
label: 'Caption',
|
||||
multiline: true,
|
||||
}),
|
||||
credit: fields.text({
|
||||
label: 'Credit/Attribution',
|
||||
description: 'Photographer, artist, or source',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default figureComponent;
|
||||
|
||||
@@ -2,12 +2,16 @@ import gridComponents from '@/keystatic/components/general/grid';
|
||||
import sidenoteComponents from '@/keystatic/components/general/sidenote';
|
||||
import definitionlistComponents from '@/keystatic/components/general/definitionlist';
|
||||
import accordionComponents from '@/keystatic/components/general/accordion';
|
||||
import blockquoteComponents from '@/keystatic/components/general/blockquote';
|
||||
import blockquoteComponent from '@/keystatic/components/general/blockquote';
|
||||
import figureComponent from '@/keystatic/components/general/figure';
|
||||
import calloutComponent from '@/keystatic/components/general/callout';
|
||||
|
||||
export const generalComponents = {
|
||||
...gridComponents,
|
||||
...sidenoteComponents,
|
||||
...definitionlistComponents,
|
||||
...accordionComponents,
|
||||
...blockquoteComponents,
|
||||
...blockquoteComponent,
|
||||
...figureComponent,
|
||||
...calloutComponent,
|
||||
};
|
||||
|
||||
@@ -93,4 +93,35 @@ export const tags: Config['tags'] = {
|
||||
},
|
||||
},
|
||||
},
|
||||
Figure: {
|
||||
render: 'Figure',
|
||||
attributes: {
|
||||
src: {
|
||||
type: 'String',
|
||||
required: true,
|
||||
},
|
||||
alt: {
|
||||
type: 'String',
|
||||
required: true,
|
||||
},
|
||||
caption: {
|
||||
type: 'String',
|
||||
},
|
||||
credit: {
|
||||
type: 'String',
|
||||
},
|
||||
},
|
||||
},
|
||||
Callout: {
|
||||
render: 'Callout',
|
||||
attributes: {
|
||||
type: {
|
||||
type: 'String',
|
||||
default: 'default',
|
||||
},
|
||||
title: {
|
||||
type: 'String',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
& p {
|
||||
& p:not([class]) {
|
||||
margin-block: var(--el-p-vspace-top) var(--el-p-vspace-bottom);
|
||||
|
||||
font-family: var(--el-p-font-family), sans-serif;
|
||||
@@ -495,18 +495,6 @@
|
||||
text-underline-offset: var(--size-1);
|
||||
}
|
||||
|
||||
& dfn {
|
||||
font-weight: var(--typo-weight-bold);
|
||||
font-style: normal;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
& cite {
|
||||
font-weight: var(--typo-weight-semibold);
|
||||
font-style: normal;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
& q {
|
||||
font-style: normal;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user