feat: responsive card pages
This commit is contained in:
69
src/pages/AddDspPage/AddDspPage.css
Normal file
69
src/pages/AddDspPage/AddDspPage.css
Normal file
@ -0,0 +1,69 @@
|
||||
.add-dsp-form__header {
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.add-dsp-form__title {
|
||||
margin: 0;
|
||||
font-size: var(--font-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.add-dsp-form__hint {
|
||||
margin: var(--space-2) 0 0;
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.add-dsp-form__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-4);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.add-dsp-form__row {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.add-dsp-form__field label {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.add-dsp-form__field input,
|
||||
.add-dsp-form__field select {
|
||||
height: var(--card-control-height);
|
||||
padding: 0 var(--space-3);
|
||||
|
||||
background: var(--bg-panel);
|
||||
|
||||
border: var(--border-width) solid var(--border-hairline);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--font-sm);
|
||||
|
||||
outline: none;
|
||||
transition: border-color var(--dur-base) var(--ease-standard);
|
||||
}
|
||||
|
||||
.add-dsp-form__field input.mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.add-dsp-form__field input:focus,
|
||||
.add-dsp-form__field select:focus {
|
||||
border-color: var(--accent-brand);
|
||||
}
|
||||
|
||||
.add-dsp-form__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-3);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
126
src/pages/AddDspPage/AddDspPage.tsx
Normal file
126
src/pages/AddDspPage/AddDspPage.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { useState } from 'react';
|
||||
import { DSP, DSP_TYPES } from '../../types/types';
|
||||
import Button from '../../components/Button/Button';
|
||||
import Card from '../../components/Card/Card';
|
||||
|
||||
import './AddDspPage.css';
|
||||
|
||||
function AddDSPForm({
|
||||
onCancel,
|
||||
onAdd,
|
||||
}: {
|
||||
onCancel: () => void;
|
||||
onAdd: (d: Omit<DSP, 'status' | 'id'>) => void;
|
||||
}) {
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
type: DSP_TYPES[0].name,
|
||||
ip: '192.168.1.100',
|
||||
port: DSP_TYPES[0].defaultPort,
|
||||
deviceId: '1',
|
||||
});
|
||||
|
||||
const canSubmit =
|
||||
form.name.trim().length > 0 && form.ip.trim().length > 0 && form.deviceId.length > 0;
|
||||
|
||||
function submit() {
|
||||
if (!canSubmit) return;
|
||||
onAdd({
|
||||
...form,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="add-dsp-form__header">
|
||||
<h1 className="add-dsp-form__title">Add DSP</h1>
|
||||
<p className="add-dsp-form__hint">Register a new device to control from this app.</p>
|
||||
</div>
|
||||
|
||||
<div className="add-dsp-form__field">
|
||||
<label htmlFor="f-name">Name</label>
|
||||
<input
|
||||
id="f-name"
|
||||
placeholder="Main Room DSP"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="add-dsp-form__row">
|
||||
<div className="add-dsp-form__field">
|
||||
<label htmlFor="f-type">Model</label>
|
||||
<select
|
||||
id="f-type"
|
||||
value={form.type}
|
||||
onChange={(e) => {
|
||||
const selected = DSP_TYPES.find((d) => d.name === e.target.value);
|
||||
|
||||
setForm({
|
||||
...form,
|
||||
type: e.target.value,
|
||||
port: selected?.defaultPort ?? form.port,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{DSP_TYPES.map((dsp) => (
|
||||
<option key={dsp.name} value={dsp.name}>
|
||||
{dsp.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="add-dsp-form__field">
|
||||
<label htmlFor="f-deviceid">Device ID</label>
|
||||
<input
|
||||
id="f-deviceid"
|
||||
className="mono"
|
||||
type="number"
|
||||
min={1}
|
||||
max={255}
|
||||
step={1}
|
||||
placeholder="1"
|
||||
value={form.deviceId}
|
||||
onChange={(e) => setForm({ ...form, deviceId: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="add-dsp-form__row">
|
||||
<div className="add-dsp-form__field">
|
||||
<label htmlFor="f-ip">IP address</label>
|
||||
<input
|
||||
id="f-ip"
|
||||
className="mono"
|
||||
value={form.ip}
|
||||
onChange={(e) => setForm({ ...form, ip: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="add-dsp-form__field">
|
||||
<label htmlFor="f-port">Port</label>
|
||||
<input
|
||||
id="f-port"
|
||||
className="mono"
|
||||
type="number"
|
||||
value={form.port}
|
||||
onChange={(e) => setForm({ ...form, port: Number(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="add-dsp-form__actions">
|
||||
<Button variant="ghost" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button variant="primary" onClick={submit} disabled={!canSubmit}>
|
||||
Add DSP
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddDSPForm;
|
||||
Reference in New Issue
Block a user