Improve showcase pages: drawers, modals, badges, and Vite caching fix
- DTDrawer: storefront-matching dual-element bevel border, slide-in/out animations from correct edge, 99vh height, bold 2em header, box shadow - DTModal: interactive demos on Bevels page with all 5 mode variants - Badges: solid color fill with contrasting text (black on dark, white on light) instead of transparent fill with colored text - Animations: add dt-slide-right and dt-slide-left keyframes for drawers - Bevels page: remove trash "Button Bevels", add interactive modal/drawer demos, fix media frame to use real images with proper bevel styling - Advanced Cards: remove redundant media frame sections, rename "Menu Items" to "Buttons", remove DTButton/dt-btn references - Forms page: rename "Menu Items" to "Buttons" - Vite config: exclude design system packages from optimizeDeps pre-bundling and watch dist directories to prevent stale CSS during development Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,24 @@
|
|||||||
/**
|
/**
|
||||||
* DTDrawer — Sliding side panel with beveled edges.
|
* DTDrawer — Sliding side panel with beveled edges.
|
||||||
*
|
*
|
||||||
|
* Matches the dt-shopify-storefront Aside pattern:
|
||||||
|
* - Outer colored shell (mode color) with beveled clip-path
|
||||||
|
* - Inner dark surface with inset bevel clip-path (creates colored border)
|
||||||
|
* - Colored header bar with bold heading + close button
|
||||||
|
* - Scrollable content area
|
||||||
|
* - Slides in and out from the correct edge
|
||||||
|
*
|
||||||
* CSS reference: bevels.css .dt-bevel-drawer-right, .dt-bevel-drawer-left
|
* CSS reference: bevels.css .dt-bevel-drawer-right, .dt-bevel-drawer-left
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useCallback, type ReactNode, type CSSProperties } from 'react';
|
import { useEffect, useState, useCallback, useRef, type ReactNode, type CSSProperties } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import type { DTVariant } from '@dangerousthings/tokens';
|
import type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { cx } from '../utils/cx';
|
import { cx } from '../utils/cx';
|
||||||
import { getVariantClass } from '../utils/variantClasses';
|
import { getVariantClass } from '../utils/variantClasses';
|
||||||
|
|
||||||
|
const DURATION = 200;
|
||||||
|
|
||||||
interface DTDrawerProps {
|
interface DTDrawerProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
@@ -36,6 +45,30 @@ export function DTDrawer({
|
|||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: DTDrawerProps) {
|
}: DTDrawerProps) {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [closing, setClosing] = useState(false);
|
||||||
|
const timerRef = useRef<ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
|
// Open: mount immediately
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
setClosing(false);
|
||||||
|
setMounted(true);
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
// Close: play exit animation, then unmount
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible && mounted && !closing) {
|
||||||
|
setClosing(true);
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setMounted(false);
|
||||||
|
setClosing(false);
|
||||||
|
}, DURATION);
|
||||||
|
}
|
||||||
|
return () => clearTimeout(timerRef.current);
|
||||||
|
}, [visible, mounted, closing]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') onDismiss();
|
if (e.key === 'Escape') onDismiss();
|
||||||
@@ -44,16 +77,21 @@ export function DTDrawer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (mounted && !closing) {
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||||
}
|
}
|
||||||
}, [visible, handleKeyDown]);
|
}, [mounted, closing, handleKeyDown]);
|
||||||
|
|
||||||
if (!visible) return null;
|
if (!mounted) return null;
|
||||||
|
|
||||||
const bevelClass = position === 'right' ? 'dt-bevel-drawer-right' : 'dt-bevel-drawer-left';
|
const bevelClass = position === 'right' ? 'dt-bevel-drawer-right' : 'dt-bevel-drawer-left';
|
||||||
|
const innerBevelClass = position === 'right' ? 'dt-bevel-drawer-right-inner' : 'dt-bevel-drawer-left-inner';
|
||||||
const widthValue = typeof width === 'number' ? `${width}px` : width;
|
const widthValue = typeof width === 'number' ? `${width}px` : width;
|
||||||
|
const varCSSVar = getVariantCSSVar(headingVariant);
|
||||||
|
|
||||||
|
// Slide direction for exit: reverse of entry
|
||||||
|
const slideOut = position === 'right' ? 'translateX(100%)' : 'translateX(-100%)';
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
@@ -61,19 +99,22 @@ export function DTDrawer({
|
|||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
|
pointerEvents: closing ? 'none' : undefined,
|
||||||
}}>
|
}}>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
background: 'rgba(0, 0, 0, 0.5)',
|
background: 'rgba(0, 0, 0, 0.2)',
|
||||||
backdropFilter: 'blur(4px)',
|
backdropFilter: 'blur(4px)',
|
||||||
animation: 'dt-fade-in 0.2s ease-in-out both',
|
animation: closing ? undefined : `dt-fade-in 0.4s ease-in-out both`,
|
||||||
|
opacity: closing ? 0 : undefined,
|
||||||
|
transition: closing ? `opacity ${DURATION}ms ease-in-out` : undefined,
|
||||||
}}
|
}}
|
||||||
onClick={onDismiss}
|
onClick={closing ? undefined : onDismiss}
|
||||||
/>
|
/>
|
||||||
{/* Panel */}
|
{/* Outer shell */}
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
bevelClass,
|
bevelClass,
|
||||||
@@ -83,28 +124,44 @@ export function DTDrawer({
|
|||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
|
||||||
[position]: 0,
|
[position]: 0,
|
||||||
|
height: '99vh',
|
||||||
width: widthValue,
|
width: widthValue,
|
||||||
maxWidth: '100vw',
|
maxWidth: '100vw',
|
||||||
|
background: `var(${varCSSVar}, var(--color-primary))`,
|
||||||
|
padding: '3px',
|
||||||
|
paddingRight: position === 'right' ? 0 : '3px',
|
||||||
|
paddingLeft: position === 'left' ? 0 : '3px',
|
||||||
|
boxShadow: '0 0 50px rgba(0, 0, 0, 0.3)',
|
||||||
|
animation: closing ? undefined : `dt-slide-${position} ${DURATION}ms ease-in-out both`,
|
||||||
|
transform: closing ? slideOut : undefined,
|
||||||
|
transition: closing ? `transform ${DURATION}ms ease-in-out` : undefined,
|
||||||
|
...style,
|
||||||
|
}}>
|
||||||
|
{/* Inner surface */}
|
||||||
|
<div
|
||||||
|
className={innerBevelClass}
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
background: 'var(--color-bg)',
|
background: 'var(--color-bg)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
animation: `dt-slide-${position === 'right' ? 'up' : 'up'} 0.2s ease-in-out both`,
|
|
||||||
...style,
|
|
||||||
}}>
|
}}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: '16px',
|
height: 64,
|
||||||
background: `var(${getVariantCSSVar(headingVariant)}, var(--color-primary))`,
|
padding: '0 1em',
|
||||||
|
background: `var(${varCSSVar}, var(--color-primary))`,
|
||||||
color: 'var(--color-bg)',
|
color: 'var(--color-bg)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
fontWeight: 700,
|
fontWeight: 900,
|
||||||
fontSize: '18px',
|
fontSize: '2em',
|
||||||
letterSpacing: '0.5px',
|
letterSpacing: '0.05em',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
borderBottom: '1px solid var(--color-bg)',
|
||||||
}}>
|
}}>
|
||||||
<span>{heading}</span>
|
<span>{heading}</span>
|
||||||
<button
|
<button
|
||||||
@@ -113,10 +170,11 @@ export function DTDrawer({
|
|||||||
background: 'none',
|
background: 'none',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
fontSize: '20px',
|
fontSize: '0.6em',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
padding: '0 8px',
|
padding: '0 8px',
|
||||||
|
opacity: 0.8,
|
||||||
}}
|
}}
|
||||||
type="button">
|
type="button">
|
||||||
✕
|
✕
|
||||||
@@ -127,6 +185,7 @@ export function DTDrawer({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { DTCard, DTMediaFrame } from '@dangerousthings/react';
|
import { useState } from 'react';
|
||||||
|
import { DTCard, DTModal, DTDrawer } from '@dangerousthings/react';
|
||||||
import type { DTVariant } from '@dangerousthings/tokens';
|
import type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { Section, Row, CodeLabel } from '../components/Section';
|
import { Section, Row, CodeLabel } from '../components/Section';
|
||||||
|
|
||||||
const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other'];
|
const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other'];
|
||||||
|
|
||||||
export function BevelsPage() {
|
export function BevelsPage() {
|
||||||
|
const [modalVariant, setModalVariant] = useState<DTVariant | null>(null);
|
||||||
|
const [drawerSide, setDrawerSide] = useState<'right' | 'left' | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="page-title">Bevels</h1>
|
<h1 className="page-title">Bevels</h1>
|
||||||
@@ -21,55 +25,59 @@ export function BevelsPage() {
|
|||||||
<CodeLabel text="<DTCard> (no title prop)" />
|
<CodeLabel text="<DTCard> (no title prop)" />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Button Bevels" description="Top-right corner cut. Filled buttons use direct clip-path, outline uses dual-element.">
|
|
||||||
<Row>
|
|
||||||
<button className="btn-primary" type="button">PRIMARY</button>
|
|
||||||
<button className="btn-secondary" type="button">SECONDARY</button>
|
|
||||||
<button className="btn-danger" type="button">DANGER</button>
|
|
||||||
</Row>
|
|
||||||
<CodeLabel text=".btn-primary | .btn-secondary | .btn-danger" />
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Badge Bevels" description="Top-right bevel with dual-element technique. Color variants via CSS custom properties.">
|
<Section title="Badge Bevels" description="Top-right bevel with dual-element technique. Color variants via CSS custom properties.">
|
||||||
<Row>
|
<Row>
|
||||||
<span className="badge">Default</span>
|
<span className="badge">DEFAULT</span>
|
||||||
<span className="badge badge-success">Success</span>
|
<span className="badge badge-success">IN STOCK</span>
|
||||||
<span className="badge badge-error">Error</span>
|
<span className="badge badge-error">SOLD OUT</span>
|
||||||
<span className="badge badge-warning">Warning</span>
|
<span className="badge badge-warning">LAB</span>
|
||||||
<span className="badge badge-info">Info</span>
|
<span className="badge badge-info">NFC</span>
|
||||||
</Row>
|
</Row>
|
||||||
<CodeLabel text=".badge | .badge-success | .badge-error | .badge-warning | .badge-info" />
|
<CodeLabel text=".badge | .badge-success | .badge-error | .badge-warning | .badge-info" />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Media Frame Bevels" description="Diagonal opposite corners (top-left + bottom-right).">
|
<Section title="Media Frame Bevels" description="Diagonal opposite corners (top-left + bottom-right). Dual-element border with inner surface fill.">
|
||||||
<DTMediaFrame>
|
<div className="grid grid-cols-3 gap-dt-4">
|
||||||
<div style={{ background: 'var(--color-primary)', width: '100%', aspectRatio: '16/9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div className="dt-bevel-media mode-normal" style={{ aspectRatio: '16/9' }}>
|
||||||
<span style={{ color: 'var(--color-bg)', fontWeight: 700 }}>MEDIA FRAME</span>
|
<img src="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=600&h=338&fit=crop" alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||||
</div>
|
</div>
|
||||||
</DTMediaFrame>
|
<div className="dt-bevel-media mode-emphasis" style={{ aspectRatio: '16/9' }}>
|
||||||
<CodeLabel text="<DTMediaFrame> or .dt-bevel-media" />
|
<img src="https://images.unsplash.com/photo-1518770660439-4636190af475?w=600&h=338&fit=crop" alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||||
|
</div>
|
||||||
|
<div className="dt-bevel-media mode-warning" style={{ aspectRatio: '16/9' }} />
|
||||||
|
</div>
|
||||||
|
<CodeLabel text=".dt-bevel-media | .dt-bevel-media (no img = 'MISSING MEDIA' placeholder)" />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Modal Bevels" description="Dual bottom bevels at bevel-lg scale.">
|
<Section title="Modals" description="Beveled card dialog with backdrop blur. Click to open, click backdrop or press Escape to dismiss.">
|
||||||
<div className="dt-bevel-modal" style={{ background: 'var(--color-primary)', padding: 'var(--space-8)', textAlign: 'center' }}>
|
|
||||||
<div style={{ background: 'var(--color-surface)', padding: 'var(--space-6)' }}>
|
|
||||||
<div style={{ fontWeight: 700, textTransform: 'uppercase' }}>Modal Content</div>
|
|
||||||
<div style={{ color: 'var(--color-text-muted)', marginTop: 'var(--space-2)' }}>Large dual bottom bevels</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<CodeLabel text=".dt-bevel-modal" />
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Drawer Bevels" description="Exposed-edge bevels for sliding panels.">
|
|
||||||
<Row>
|
<Row>
|
||||||
<div className="dt-bevel-drawer-right" style={{ background: 'var(--color-primary)', padding: 'var(--space-6)', width: 200, height: 120, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
{modes.map(mode => (
|
||||||
<span style={{ color: 'var(--color-bg)', fontWeight: 700 }}>RIGHT</span>
|
<button key={mode} className={`dt-menu-item mode-${mode}`} type="button" onClick={() => setModalVariant(mode)}>
|
||||||
</div>
|
{mode.toUpperCase()} MODAL
|
||||||
<div className="dt-bevel-drawer-left" style={{ background: 'var(--color-primary)', padding: 'var(--space-6)', width: 200, height: 120, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
</button>
|
||||||
<span style={{ color: 'var(--color-bg)', fontWeight: 700 }}>LEFT</span>
|
))}
|
||||||
</div>
|
|
||||||
</Row>
|
</Row>
|
||||||
<CodeLabel text=".dt-bevel-drawer-right | .dt-bevel-drawer-left" />
|
<CodeLabel text="<DTModal variant='normal' visible onDismiss title='...'>content</DTModal>" />
|
||||||
|
<DTModal visible={modalVariant !== null} onDismiss={() => setModalVariant(null)} variant={modalVariant ?? 'normal'} title={`${(modalVariant ?? 'normal').toUpperCase()} MODAL`}>
|
||||||
|
<p style={{ marginBottom: 'var(--space-4)' }}>This is a <strong>{modalVariant}</strong> modal with beveled card shape, backdrop blur, and scale-in animation.</p>
|
||||||
|
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.85rem' }}>Click the backdrop or press Escape to dismiss.</p>
|
||||||
|
</DTModal>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Drawers" description="Sliding side panel with beveled edges. Click to open from either side.">
|
||||||
|
<Row>
|
||||||
|
<button className="dt-menu-item mode-emphasis" type="button" onClick={() => setDrawerSide('right')}>
|
||||||
|
OPEN RIGHT DRAWER
|
||||||
|
</button>
|
||||||
|
<button className="dt-menu-item mode-other" type="button" onClick={() => setDrawerSide('left')}>
|
||||||
|
OPEN LEFT DRAWER
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
<CodeLabel text="<DTDrawer position='right' heading='...' visible onDismiss>content</DTDrawer>" />
|
||||||
|
<DTDrawer visible={drawerSide !== null} onDismiss={() => setDrawerSide(null)} position={drawerSide ?? 'right'} heading={`${(drawerSide ?? 'right').toUpperCase()} DRAWER`} headingVariant={drawerSide === 'left' ? 'other' : 'emphasis'}>
|
||||||
|
<p style={{ marginBottom: 'var(--space-4)' }}>Sliding panel from the <strong>{drawerSide}</strong> edge with beveled corners and backdrop blur.</p>
|
||||||
|
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.85rem' }}>Click the backdrop, press Escape, or click ✕ to dismiss.</p>
|
||||||
|
</DTDrawer>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Small Bevel Utility" description="For compact elements — arrows, stepper buttons.">
|
<Section title="Small Bevel Utility" description="For compact elements — arrows, stepper buttons.">
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { createElement, useState, type CSSProperties } from 'react';
|
import { createElement, useState, type CSSProperties } from 'react';
|
||||||
import {
|
import {
|
||||||
DTCard,
|
DTCard,
|
||||||
DTButton,
|
|
||||||
DTStaggerContainer,
|
DTStaggerContainer,
|
||||||
DTFeatureLegend,
|
DTFeatureLegend,
|
||||||
} from '@dangerousthings/react';
|
} from '@dangerousthings/react';
|
||||||
import type { DTFeatureItem } from '@dangerousthings/react';
|
import type { DTFeatureItem } from '@dangerousthings/react';
|
||||||
import type { DTVariant } from '@dangerousthings/tokens';
|
import type { DTVariant } from '@dangerousthings/tokens';
|
||||||
import { Section, Row, CodeLabel } from '../components/Section';
|
import { Section, CodeLabel } from '../components/Section';
|
||||||
|
|
||||||
// Feature icons from dt-shopify-storefront
|
// Feature icons from dt-shopify-storefront
|
||||||
import {
|
import {
|
||||||
@@ -91,7 +90,7 @@ export function CardsAdvancedPage() {
|
|||||||
<CodeLabel text="<DTCard progress={50} /> — height driven by --dt-card-progress" />
|
<CodeLabel text="<DTCard progress={50} /> — height driven by --dt-card-progress" />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Card Badges" description="Bottom-right chip badge on product image area. Inherits card mode color.">
|
<Section title="Card Badges" description="Bottom-right chip badge on card product image area. Inherits card mode color.">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-dt-4">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-dt-4">
|
||||||
{[
|
{[
|
||||||
{ label: 'LAB', mode: 'warning' as DTVariant, img: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=400&h=400&fit=crop' },
|
{ label: 'LAB', mode: 'warning' as DTVariant, img: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=400&h=400&fit=crop' },
|
||||||
@@ -106,52 +105,10 @@ export function CardsAdvancedPage() {
|
|||||||
</DTCard>
|
</DTCard>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<CodeLabel text=".dt-badge-parent > .dt-card-badge — badge positioned on image container" />
|
<CodeLabel text=".dt-badge-parent > .dt-card-badge — badge positioned on card image container" />
|
||||||
|
|
||||||
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', margin: 'var(--space-4) 0 var(--space-2)' }}>
|
|
||||||
Also works on beveled media frames:
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-3 gap-dt-4">
|
|
||||||
{[
|
|
||||||
{ label: 'LAB', mode: 'warning' as DTVariant, img: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=400&h=400&fit=crop' },
|
|
||||||
{ label: 'BUNDLE', mode: 'other' as DTVariant, img: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&h=400&fit=crop' },
|
|
||||||
{ label: 'NFC', mode: 'normal' as DTVariant, img: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=400&h=400&fit=crop' },
|
|
||||||
].map(badge => (
|
|
||||||
<div key={badge.label} className={`dt-bevel-media mode-${badge.mode}`} style={{ aspectRatio: '1', overflow: 'hidden' }}>
|
|
||||||
<img src={badge.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
||||||
<span className="dt-card-badge">{badge.label}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<CodeLabel text=".dt-bevel-media.mode-warning > .dt-card-badge" />
|
|
||||||
|
|
||||||
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', margin: 'var(--space-4) 0 var(--space-2)' }}>
|
|
||||||
Missing image placeholder (no img element):
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-3 gap-dt-4">
|
|
||||||
{(['warning', 'other', 'normal'] as DTVariant[]).map(mode => (
|
|
||||||
<div key={mode} className={`dt-bevel-media mode-${mode}`} style={{ aspectRatio: '1' }} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<CodeLabel text=".dt-bevel-media without child img — shows 'MISSING MEDIA' placeholder" />
|
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Interactive Bevel Buttons" description="Outlined rectangle by default. Bottom-right bevel appears on hover/select, fills with mode color.">
|
<Section title="Buttons" description="Beveled buttons with active/selected states, mode colors, and level indentation.">
|
||||||
<Row>
|
|
||||||
{modes.map(mode => (
|
|
||||||
<DTButton key={mode} variant={mode}>{mode.toUpperCase()}</DTButton>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
<CodeLabel text="<DTButton variant='normal'> — hover for bevel + fill" />
|
|
||||||
<Row style={{ marginTop: 'var(--space-4)' }}>
|
|
||||||
{modes.map(mode => (
|
|
||||||
<DTButton key={mode} variant={mode} selected>{mode.toUpperCase()} SEL</DTButton>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
<CodeLabel text="<DTButton variant='emphasis' selected /> — persistent bevel + fill" />
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title="Menu Items" description="Filter-style beveled menu items with active/selected states and level indentation.">
|
|
||||||
<div style={{ maxWidth: 300 }}>
|
<div style={{ maxWidth: 300 }}>
|
||||||
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((item, i) => (
|
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((item, i) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function FormsPage() {
|
|||||||
<CodeLabel text=".dt-filter-header | .dt-filter-header.active" />
|
<CodeLabel text=".dt-filter-header | .dt-filter-header.active" />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="Menu Items" description="Beveled filter menu items with active state and level indentation.">
|
<Section title="Buttons" description="Beveled buttons with active state, mode colors, and level indentation.">
|
||||||
<div style={{ maxWidth: 300 }}>
|
<div style={{ maxWidth: 300 }}>
|
||||||
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((name, i) => (
|
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((name, i) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -15,4 +15,16 @@ export default defineConfig({
|
|||||||
'@dangerousthings/web': path.resolve(__dirname, '../../web/dist'),
|
'@dangerousthings/web': path.resolve(__dirname, '../../web/dist'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
// Don't pre-bundle design system packages — they're local workspace deps
|
||||||
|
// that change frequently during development. Pre-bundling caches them in
|
||||||
|
// node_modules/.vite which causes stale CSS/JS after rebuilds.
|
||||||
|
exclude: ['@dangerousthings/web', '@dangerousthings/react', '@dangerousthings/tokens'],
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
// Watch design system dist directories for changes during dev
|
||||||
|
watch: {
|
||||||
|
ignored: ['!**/packages/web/dist/**', '!**/packages/react/dist/**', '!**/packages/tokens/dist/**'],
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,16 @@
|
|||||||
to { transform: translateY(0); }
|
to { transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes dt-slide-right {
|
||||||
|
from { transform: translateX(100%); }
|
||||||
|
to { transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dt-slide-left {
|
||||||
|
from { transform: translateX(-100%); }
|
||||||
|
to { transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Interactive Animations
|
Interactive Animations
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|||||||
@@ -228,36 +228,48 @@
|
|||||||
2px calc(100% - 2px));
|
2px calc(100% - 2px));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Colored badge variants — solid color fill with contrasting text.
|
||||||
|
Dark mode: black text on bright neon fill.
|
||||||
|
Light mode: white text on bright fill (via --color-bg which flips per theme). */
|
||||||
[data-brand="dt"] .badge-success {
|
[data-brand="dt"] .badge-success {
|
||||||
--badge-border-color: var(--color-success);
|
--badge-border-color: var(--color-success);
|
||||||
--badge-fill: rgba(var(--color-success-rgb), 0.25);
|
--badge-fill: var(--color-success);
|
||||||
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-brand="dt"] .badge-error {
|
[data-brand="dt"] .badge-error {
|
||||||
--badge-border-color: var(--color-error);
|
--badge-border-color: var(--color-error);
|
||||||
--badge-fill: rgba(var(--color-error-rgb), 0.25);
|
--badge-fill: var(--color-error);
|
||||||
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-brand="dt"] .badge-warning {
|
[data-brand="dt"] .badge-warning {
|
||||||
--badge-border-color: var(--color-warning);
|
--badge-border-color: var(--color-warning);
|
||||||
--badge-fill: rgba(var(--color-warning-rgb), 0.25);
|
--badge-fill: var(--color-warning);
|
||||||
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-brand="dt"] .badge-info {
|
[data-brand="dt"] .badge-info {
|
||||||
--badge-border-color: var(--color-info);
|
--badge-border-color: var(--color-info);
|
||||||
--badge-fill: rgba(var(--color-info-rgb), 0.25);
|
--badge-fill: var(--color-info);
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Classic brand colored badges — same solid fill approach */
|
||||||
|
[data-brand="classic"] .badge-success {
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-brand="classic"] .badge-error {
|
||||||
|
color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-brand="classic"] .badge-warning {
|
||||||
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
|
||||||
Badge text override for Classic — ensure readable text in light mode
|
|
||||||
Status-color text on 25% opacity backgrounds fails WCAG AA on light bg.
|
|
||||||
Use primary text color instead (same approach as DT badges above).
|
|
||||||
============================================================================ */
|
|
||||||
[data-brand="classic"] .badge-success,
|
|
||||||
[data-brand="classic"] .badge-error,
|
|
||||||
[data-brand="classic"] .badge-warning,
|
|
||||||
[data-brand="classic"] .badge-info {
|
[data-brand="classic"] .badge-info {
|
||||||
color: var(--color-text-primary);
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
@@ -386,6 +398,16 @@
|
|||||||
0% calc(100% - var(--bevel-lg)));
|
0% calc(100% - var(--bevel-lg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inner surface clip — inset from outer to create colored border frame */
|
||||||
|
.dt-bevel-drawer-right-inner {
|
||||||
|
clip-path: polygon(3px calc(var(--bevel-lg) + 1px),
|
||||||
|
calc(var(--bevel-lg) + 1px) 3px,
|
||||||
|
100% 3px,
|
||||||
|
100% calc(100% - 3px),
|
||||||
|
calc(var(--bevel-lg) + 1px) calc(100% - 3px),
|
||||||
|
3px calc(100% - var(--bevel-lg) - 1px));
|
||||||
|
}
|
||||||
|
|
||||||
.dt-bevel-drawer-left {
|
.dt-bevel-drawer-left {
|
||||||
clip-path: polygon(0% 0%,
|
clip-path: polygon(0% 0%,
|
||||||
calc(100% - var(--bevel-lg)) 0%,
|
calc(100% - var(--bevel-lg)) 0%,
|
||||||
@@ -395,6 +417,16 @@
|
|||||||
0% 100%);
|
0% 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inner surface clip — inset from outer to create colored border frame */
|
||||||
|
.dt-bevel-drawer-left-inner {
|
||||||
|
clip-path: polygon(0% 3px,
|
||||||
|
calc(100% - var(--bevel-lg) - 1px) 3px,
|
||||||
|
calc(100% - 3px) calc(var(--bevel-lg) + 1px),
|
||||||
|
calc(100% - 3px) calc(100% - var(--bevel-lg) - 1px),
|
||||||
|
calc(100% - var(--bevel-lg) - 1px) calc(100% - 3px),
|
||||||
|
0% calc(100% - 3px));
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
Small Bevels — for compact elements (arrows, stepper buttons)
|
Small Bevels — for compact elements (arrows, stepper buttons)
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|||||||
Reference in New Issue
Block a user