Remove glows, refine feature legend, forms, and showcase pages

- Delete glows.css and GlowsPage (unused)
- Enhance DTFeatureLegend and feature-legend.css with improved layout
- Update DTRadioGroup styling
- Refine bevels.css, forms-dt.css
- Update AnimationsPage, CardsAdvancedPage, FormsPage, HomePage
- Add type:module to web package.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-08 10:03:48 -07:00
parent 54b58a6ac6
commit 0cfa83259e
13 changed files with 384 additions and 283 deletions

View File

@@ -1,20 +1,25 @@
/**
* DTFeatureLegend — Grid of product feature icons with rotated labels.
* DTFeatureLegend — Interactive grid of product feature icons with rotated labels.
*
* CSS reference: feature-legend.css .dt-feature-legend, .dt-feature-legend-header,
* .dt-feature-legend-grid, .dt-feature-legend-item, .dt-feature-supported, etc.
* Matches dt-shopify-storefront UseCaseLegend: header bar with hover details,
* rotated vertical labels (above for row 0, below for row 1), ? toggle,
* bordered grid with dark cell backgrounds, and pulse-on-hover icons.
*
* CSS reference: feature-legend.css
*/
import type { ReactNode, CSSProperties } from 'react';
import { useState, type ReactNode, type CSSProperties } from 'react';
import type { DTVariant } from '@dangerousthings/tokens';
import { cx } from '../utils/cx';
import { getVariantClass, featureStateToVariant } from '../utils/variantClasses';
import { getVariantClass } from '../utils/variantClasses';
export interface DTFeatureItem {
key: string;
name: string;
icon: ReactNode;
state: 'supported' | 'disabled' | 'unsupported';
/** Optional detail text shown in the header on hover */
detail?: string;
}
const stateClassMap: Record<DTFeatureItem['state'], string> = {
@@ -32,6 +37,8 @@ interface DTFeatureLegendProps {
columns?: number;
/** Icon size in px @default 42 */
iconSize?: number;
/** Show rotated labels initially @default true */
showLabels?: boolean;
className?: string;
style?: CSSProperties;
}
@@ -42,35 +49,93 @@ export function DTFeatureLegend({
variant = 'normal',
columns = 5,
iconSize = 42,
showLabels: initialShowLabels = true,
className,
style,
}: DTFeatureLegendProps) {
// Suppress unused import warning — featureStateToVariant is available for consumers
void featureStateToVariant;
const [showLabels, setShowLabels] = useState(initialShowLabels);
const [hoveredFeature, setHoveredFeature] = useState<string | null>(null);
const hoveredItem = hoveredFeature
? features.find(f => f.key === hoveredFeature)
: null;
const rowCount = Math.ceil(features.length / columns);
const hasBottomRow = rowCount >= 2;
return (
<div
className={cx('dt-feature-legend', getVariantClass(variant), className)}
style={style}>
{/* Header bar — title + hovered feature detail + ? toggle */}
{title && (
<div className="dt-feature-legend-header">{title}</div>
)}
<div
className="dt-feature-legend-grid"
style={{ '--dt-feature-columns': columns } as CSSProperties}>
{features.map(feature => (
<div
key={feature.key}
className={cx('dt-feature-legend-item', stateClassMap[feature.state])}
style={{ width: `${100 / columns}%` }}>
<div
className="dt-feature-legend-icon"
style={{ width: `${iconSize}px`, height: `${iconSize}px` }}>
{feature.icon}
<div className="dt-feature-legend-header">
<div className="dt-feature-legend-header-content">
<div className="dt-feature-legend-title">{title}</div>
<div className="dt-feature-legend-detail">
{hoveredItem ? (
<span className={stateClassMap[hoveredItem.state]}>
{hoveredItem.name}{hoveredItem.detail ? ': ' : ''}
{hoveredItem.detail && <strong>{hoveredItem.detail}</strong>}
</span>
) : (
<span className="dt-feature-legend-hint">
Hover a feature for details
</span>
)}
</div>
<div className="dt-feature-legend-label">{feature.name}</div>
</div>
))}
<button
className="dt-feature-legend-toggle"
onClick={() => setShowLabels(v => !v)}
aria-label={showLabels ? 'Hide feature labels' : 'Show feature labels'}
title={showLabels ? 'Hide labels' : 'Show labels'}
type="button">
?
</button>
</div>
)}
{/* Icon grid with border */}
<div className="dt-feature-legend-body">
<div
className={cx(
'dt-feature-legend-grid',
showLabels && 'with-labels',
showLabels && hasBottomRow && 'with-labels-bottom',
)}
style={{ '--dt-feature-columns': columns } as CSSProperties}>
{features.map((feature, index) => {
const row = Math.floor(index / columns);
const labelPosition = rowCount >= 2 ? (row === 0 ? 'above' : 'below') : 'above';
return (
<div
key={feature.key}
className={cx('dt-feature-legend-item', stateClassMap[feature.state])}
style={{ width: `${100 / columns}%` }}
onMouseEnter={() => setHoveredFeature(feature.key)}
onMouseLeave={() => setHoveredFeature(null)}>
{/* Rotated vertical label — always in DOM, visibility via CSS */}
<div className={cx(
'dt-feature-legend-rotated-label',
labelPosition === 'below' ? 'label-below' : 'label-above',
showLabels && 'label-visible',
)}>
<span>{feature.name}</span>
</div>
<div
className="dt-feature-legend-icon"
style={{ width: `${iconSize}px`, height: `${iconSize}px` }}>
{feature.icon}
</div>
</div>
);
})}
</div>
</div>
</div>
);

View File

@@ -1,7 +1,10 @@
/**
* DTRadioGroup — Hexagonal radio buttons.
*
* CSS reference: forms-dt.css input[type="radio"], .dt-radio-option
* Uses a custom span for the hexagon indicator since ::before pseudo-elements
* on <input> are unreliable in Chromium/Electron.
*
* CSS reference: forms-dt.css .dt-radio-hex, .dt-radio-option
*/
import { useId, type CSSProperties } from 'react';
@@ -39,20 +42,27 @@ export function DTRadioGroup({
className={cx('dt-radio-group', className)}
style={{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }}
role="radiogroup">
{options.map(option => (
<label
key={option.value}
className={cx('dt-radio-option', value === option.value && 'selected')}>
<input
type="radio"
name={groupName}
value={option.value}
checked={value === option.value}
onChange={() => onChange(option.value)}
/>
<span>{option.label}</span>
</label>
))}
{options.map(option => {
const checked = value === option.value;
return (
<label
key={option.value}
className={cx('dt-radio-option', checked && 'selected')}>
<input
type="radio"
name={groupName}
value={option.value}
checked={checked}
onChange={() => onChange(option.value)}
style={{ position: 'absolute', opacity: 0, width: 0, height: 0 }}
/>
<span className={cx('dt-radio-hex', checked && 'checked')}>
<span className="dt-radio-hex-inner" />
</span>
<span>{option.label}</span>
</label>
);
})}
</div>
);
}