Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/components/editor/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import { useId } from 'react';

interface ColorPickerProps {
label: string;
value: string;
onChange: (value: string) => void;
}

export function ColorPicker({ label, value, onChange }: ColorPickerProps) {
const id = useId();
const colorId = `${id}-color`;
const hexId = `${id}-hex`;
const displayValue = value.startsWith('#') ? value : `#${value}`;

return (
<div className="editor-field">
<label className="editor-field-label">{label}</label>
<label htmlFor={colorId} className="editor-field-label">
{label}
</label>

<div className="editor-color-row">
<input
id={colorId}
type="color"
className="editor-color-swatch"
value={displayValue}
onChange={(e) => onChange(e.target.value)}
/>

<input
id={hexId}
type="text"
aria-label={`${label} hex value`}
className="editor-field-input editor-color-text"
value={displayValue}
onChange={(e) => onChange(e.target.value)}
Expand Down
9 changes: 8 additions & 1 deletion src/components/editor/FontSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useId } from 'react';

interface FontSelectorProps {
label: string;
value: string;
Expand All @@ -12,12 +14,17 @@ const defaultFontOptions = [
];

export function FontSelector({ label, value, onChange, options }: FontSelectorProps) {
const id = useId();
const opts = options || defaultFontOptions;

return (
<div className="editor-field">
<label className="editor-field-label">{label}</label>
<label htmlFor={id} className="editor-field-label">
{label}
</label>

<select
id={id}
className="editor-field-input editor-select"
value={value}
onChange={(e) => onChange(e.target.value)}
Expand Down
24 changes: 20 additions & 4 deletions src/components/editor/ImageUploader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useId } from 'react';

interface ImageUploaderProps {
label: string;
Expand All @@ -7,10 +7,15 @@ interface ImageUploaderProps {
}

export function ImageUploader({ label, value, onChange }: ImageUploaderProps) {
const id = useId();
const urlId = `${id}-url`;
const fileId = `${id}-file`;

const handleFile = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;

const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result === 'string') {
Expand All @@ -24,17 +29,28 @@ export function ImageUploader({ label, value, onChange }: ImageUploaderProps) {

return (
<div className="editor-field">
<label className="editor-field-label">{label}</label>
<label htmlFor={urlId} className="editor-field-label">
{label}
</label>

<input
id={urlId}
type="text"
className="editor-field-input"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Image URL or upload..."
/>
<label className="editor-upload-btn">

<input
id={fileId}
className="editor-file-input"
type="file"
accept="image/*"
onChange={handleFile}
/>
<label htmlFor={fileId} className="editor-upload-btn">
Upload
<input type="file" accept="image/*" onChange={handleFile} hidden />
</label>
</div>
);
Expand Down
10 changes: 9 additions & 1 deletion src/components/editor/SliderControl.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useId } from 'react';

interface SliderControlProps {
label: string;
value: number;
Expand All @@ -7,13 +9,19 @@ interface SliderControlProps {
}

export function SliderControl({ label, value, onChange, min = 0, max = 100 }: SliderControlProps) {
const id = useId();

return (
<div className="editor-field">
<div className="editor-field-label-row">
<label className="editor-field-label">{label}</label>
<label htmlFor={id} className="editor-field-label">
{label}
</label>
<span className="editor-field-value">{value}</span>
</div>

<input
id={id}
type="range"
className="editor-slider"
value={value}
Expand Down
10 changes: 9 additions & 1 deletion src/components/editor/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { useId } from 'react';

interface ToggleSwitchProps {
label: string;
value: boolean;
onChange: (value: boolean) => void;
}

export function ToggleSwitch({ label, value, onChange }: ToggleSwitchProps) {
const id = useId();

return (
<div className="editor-field editor-toggle-row">
<label className="editor-field-label">{label}</label>
<span id={id} className="editor-field-label">
{label}
</span>

<button
type="button"
className={`editor-toggle ${value ? 'active' : ''}`}
onClick={() => onChange(!value)}
aria-labelledby={id}
role="switch"
aria-checked={value}
>
Expand Down
18 changes: 17 additions & 1 deletion src/styles/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,24 @@
cursor: pointer;
box-shadow: var(--shadow-sm);
}

/* Upload Button */
.editor-file-input {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}

.editor-file-input:focus-visible + .editor-upload-btn {
outline: 2px solid var(--accent-primary);
outline-offset: 2px;
}

.editor-upload-btn {
display: inline-flex;
align-items: center;
Expand Down
Loading