Color inputs
This commit is contained in:
parent
6579a3ba52
commit
9232a9a0d6
|
|
@ -26,6 +26,7 @@
|
||||||
"vite-plugin-svgr": "^2.2.1"
|
"vite-plugin-svgr": "^2.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@uiw/react-color-colorful": "^2.1.1",
|
||||||
"preact": "^10.10.6",
|
"preact": "^10.10.6",
|
||||||
"react-query": "^3.39.2",
|
"react-query": "^3.39.2",
|
||||||
"wouter": "^2.8.0-alpha.2"
|
"wouter": "^2.8.0-alpha.2"
|
||||||
|
|
|
||||||
|
|
@ -39,48 +39,83 @@ textarea {
|
||||||
color: var(--input-appendix-fg-color);
|
color: var(--input-appendix-fg-color);
|
||||||
background-color: var(--input-appendix-bg-color);
|
background-color: var(--input-appendix-bg-color);
|
||||||
|
|
||||||
>span {
|
> span {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 0.3rem;
|
padding: 0 0.3rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
>button {
|
> button {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.current-color {
|
||||||
|
width: 2rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border: 1px solid var(--input-border-color);
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
margin-top: -280px;
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 125%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--box-bg-color);
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid var(--input-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox-label {
|
.checkbox-label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-label input[type="checkbox"] {
|
.checkbox-label input[type='checkbox'] {
|
||||||
margin-top: 6px;
|
margin-top: 3px;
|
||||||
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input.buttons {
|
.input.buttons {
|
||||||
.button-picker {
|
.button-picker {
|
||||||
>button {
|
> button {
|
||||||
margin: 0.1rem;
|
margin: 0.1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input.date-time {
|
.input.date-time {
|
||||||
>div {
|
> div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="date"] {
|
input[type='date'] {
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="time"] {
|
input[type='time'] {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +144,7 @@ textarea {
|
||||||
color: var(--input-hint-color);
|
color: var(--input-hint-color);
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
margin-top: 0rem;
|
margin-top: 0rem;
|
||||||
margin-bottom: 0.50rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control {
|
.control {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { forwardRef, useState } from 'preact/compat'
|
||||||
|
import Colorful from '@uiw/react-color-colorful'
|
||||||
|
import { CancelIcon } from '@/icons'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string
|
||||||
|
onChange: (e: Event) => void
|
||||||
|
name: string
|
||||||
|
ref: (el: HTMLInputElement | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CssColorInput = forwardRef<HTMLInputElement, Props>(
|
||||||
|
({ name, value, ref, onChange }: Props) => {
|
||||||
|
const [showPicker, setShowPicker] = useState(false)
|
||||||
|
|
||||||
|
const handleChange = (e: Event) => {
|
||||||
|
onChange(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="color-input">
|
||||||
|
{showPicker && (
|
||||||
|
<div className="color-picker">
|
||||||
|
<div className="title">
|
||||||
|
<div className="value">{value}</div>
|
||||||
|
<div className="close" onClick={() => setShowPicker(false)}>
|
||||||
|
<CancelIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Colorful
|
||||||
|
color={value}
|
||||||
|
onChange={(color: { hex: string }) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
onChange({ target: { name, value: color.hex } } as any)
|
||||||
|
}
|
||||||
|
disableAlpha
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="current-color"
|
||||||
|
style={{ background: value }}
|
||||||
|
onClick={() => setShowPicker(true)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
ref={ref}
|
||||||
|
name={name}
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { CssColorInput } from '@/components/CssColorInput'
|
||||||
import { FormCheckboxField } from '@/components/FormCheckboxField'
|
import { FormCheckboxField } from '@/components/FormCheckboxField'
|
||||||
import { FormField } from '@/components/FormField'
|
import { FormField } from '@/components/FormField'
|
||||||
import { DashboardMQTTButtonData } from '@/utils/dashboard/parseDashboard'
|
import { DashboardMQTTButtonData } from '@/utils/dashboard/parseDashboard'
|
||||||
|
|
@ -46,7 +47,7 @@ export const MQTTButtonSettings = ({ value, onChange }: Props) => {
|
||||||
<option value="2">2</option>
|
<option value="2">2</option>
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormCheckboxField name="retain" label="Retain">
|
<FormCheckboxField label="Retain">
|
||||||
<input type="checkbox" {...register('retain')} />
|
<input type="checkbox" {...register('retain')} />
|
||||||
</FormCheckboxField>
|
</FormCheckboxField>
|
||||||
<FormField name="topic" label="Topic">
|
<FormField name="topic" label="Topic">
|
||||||
|
|
@ -56,12 +57,32 @@ export const MQTTButtonSettings = ({ value, onChange }: Props) => {
|
||||||
<textarea required {...register('message')} rows={4} />
|
<textarea required {...register('message')} rows={4} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormCheckboxField
|
<FormCheckboxField
|
||||||
name="stretchButton"
|
|
||||||
label="Stretch button to full size"
|
label="Stretch button to full size"
|
||||||
hint="Makes button cover the whole area of the box"
|
hint="Makes button cover the whole area of the box"
|
||||||
>
|
>
|
||||||
<input type="checkbox" {...register('stretchButton')} />
|
<input type="checkbox" {...register('stretchButton')} />
|
||||||
</FormCheckboxField>
|
</FormCheckboxField>
|
||||||
|
<FormField
|
||||||
|
name="fontSize"
|
||||||
|
label="Font Size"
|
||||||
|
hint="Text size multiplier, default 1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
{...register('fontSize', { type: 'number' })}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField
|
||||||
|
name="color"
|
||||||
|
label="Text Color"
|
||||||
|
hint="Input any CSS compatible value"
|
||||||
|
>
|
||||||
|
<CssColorInput {...register('textColor')} />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Background Color" hint="Input any CSS compatible value">
|
||||||
|
<CssColorInput {...register('backgroundColor')} />
|
||||||
|
</FormField>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,17 @@ export const BoxMQTTButtonContent = ({ data, ...boxProps }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableBox {...boxProps} onRefresh={() => null} hiddenTitle>
|
<EditableBox {...boxProps} onRefresh={() => null} hiddenTitle hideRefresh>
|
||||||
<div className={cn('mqtt-button', data.stretchButton && 'apply-stretch')}>
|
<div className={cn('mqtt-button', data.stretchButton && 'apply-stretch')}>
|
||||||
<button onClick={onClick} disabled={pushMutation.isLoading}>
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={pushMutation.isLoading}
|
||||||
|
style={{
|
||||||
|
fontSize: data.fontSize ? `${data.fontSize * 100}%` : undefined,
|
||||||
|
background: data.backgroundColor,
|
||||||
|
color: data.textColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{boxProps.box.title ?? 'BUTTON'}
|
{boxProps.box.title ?? 'BUTTON'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,14 @@ export type EditableBoxProps = {
|
||||||
|
|
||||||
type EditableBoxPropsWithExtra = EditableBoxProps & {
|
type EditableBoxPropsWithExtra = EditableBoxProps & {
|
||||||
hiddenTitle?: boolean
|
hiddenTitle?: boolean
|
||||||
|
hideRefresh?: boolean
|
||||||
children: ComponentChild
|
children: ComponentChild
|
||||||
onRefresh: () => void
|
onRefresh: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditableBox = ({
|
export const EditableBox = ({
|
||||||
hiddenTitle,
|
hiddenTitle,
|
||||||
|
hideRefresh,
|
||||||
box,
|
box,
|
||||||
children,
|
children,
|
||||||
onPosition,
|
onPosition,
|
||||||
|
|
@ -143,9 +145,11 @@ export const EditableBox = ({
|
||||||
{!hiddenTitle && <div className="name">{box.title || ''}</div>}
|
{!hiddenTitle && <div className="name">{box.title || ''}</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<div className="action" onClick={onRefresh}>
|
{!hideRefresh && (
|
||||||
<RefreshIcon />
|
<div className="action" onClick={onRefresh}>
|
||||||
</div>
|
<RefreshIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="action" onClick={() => setEditing(true)}>
|
<div className="action" onClick={() => setEditing(true)}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ export type DashboardMQTTButtonData = {
|
||||||
message: string
|
message: string
|
||||||
topic: string
|
topic: string
|
||||||
stretchButton?: boolean
|
stretchButton?: boolean
|
||||||
|
fontSize?: number
|
||||||
|
textColor?: string
|
||||||
|
backgroundColor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseDashboard = (input: string) => {
|
export const parseDashboard = (input: string) => {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@
|
||||||
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"],
|
||||||
"@shared/*": ["../shared/src/*"]
|
"@shared/*": ["../shared/src/*"],
|
||||||
|
"react": ["./node_modules/preact/compat/"],
|
||||||
|
"react-dom": ["./node_modules/preact/compat/"]
|
||||||
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
|
|
||||||
|
|
@ -803,6 +803,84 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@uiw/color-convert@npm:2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "@uiw/color-convert@npm:2.1.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/runtime": ">=7.19.0"
|
||||||
|
checksum: e7c526ab880416c244e04762a3652f5324d93134b558cf945fbaa74953ba8c5e2ce320e5adae88b28791a852978f8d00118623d8157d0be1b033eb1f3db352dc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@uiw/react-color-alpha@npm:2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "@uiw/react-color-alpha@npm:2.1.1"
|
||||||
|
dependencies:
|
||||||
|
"@uiw/color-convert": 2.1.1
|
||||||
|
"@uiw/react-drag-event-interactive": 2.1.1
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/runtime": ">=7.19.0"
|
||||||
|
react: ">=16.9.0"
|
||||||
|
react-dom: ">=16.9.0"
|
||||||
|
checksum: 2bf6996e71c58d16ec19bcfdceeeb4ea987eff253369dcb75afe057fa3b6557cf03f0859b052f48c6f2417a9a5b9fd006b946ee5e82701addf969bf8d7b2bac0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@uiw/react-color-colorful@npm:^2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "@uiw/react-color-colorful@npm:2.1.1"
|
||||||
|
dependencies:
|
||||||
|
"@uiw/color-convert": 2.1.1
|
||||||
|
"@uiw/react-color-alpha": 2.1.1
|
||||||
|
"@uiw/react-color-hue": 2.1.1
|
||||||
|
"@uiw/react-color-saturation": 2.1.1
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/runtime": ">=7.19.0"
|
||||||
|
react: ">=16.9.0"
|
||||||
|
react-dom: ">=16.9.0"
|
||||||
|
checksum: 7e2ba3eae5a262fe96cac95e870b966c27379f60f6502536c81b8d6ddb57023a3c9992917b6a547736787871c4dad7e050dd0d78937f2a693a2f13c10fa2551d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@uiw/react-color-hue@npm:2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "@uiw/react-color-hue@npm:2.1.1"
|
||||||
|
dependencies:
|
||||||
|
"@uiw/color-convert": 2.1.1
|
||||||
|
"@uiw/react-color-alpha": 2.1.1
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/runtime": ">=7.19.0"
|
||||||
|
react: ">=16.9.0"
|
||||||
|
react-dom: ">=16.9.0"
|
||||||
|
checksum: 7e76bbde85019bc1358b70094542089d492f2bb8d295f51294ed5175accc31f4ccd8f448930b3a0437750bb82f1318cb6ec5630acde11c39dd1e03e9e4f4a51f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@uiw/react-color-saturation@npm:2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "@uiw/react-color-saturation@npm:2.1.1"
|
||||||
|
dependencies:
|
||||||
|
"@uiw/color-convert": 2.1.1
|
||||||
|
"@uiw/react-drag-event-interactive": 2.1.1
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/runtime": ">=7.19.0"
|
||||||
|
react: ">=16.9.0"
|
||||||
|
react-dom: ">=16.9.0"
|
||||||
|
checksum: efef55093ca05a49efbac022efedfc935e925489e42bf1cbac3428ff4b6e85a6401b26a48cda7b960bc922b4bd0a64486386b31999a9eb6d466b458e771c7030
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@uiw/react-drag-event-interactive@npm:2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "@uiw/react-drag-event-interactive@npm:2.1.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/runtime": ">=7.19.0"
|
||||||
|
react: ">=16.9.0"
|
||||||
|
react-dom: ">=16.9.0"
|
||||||
|
checksum: 93d82b2a9ed89d9456187a16ab42d37ac7017e959f7a62e3c15594d0f22e091fe5c1cf805698f853f35902298055992f1d915527b0f488f1fc8d1ef821e6141e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"abbrev@npm:1":
|
"abbrev@npm:1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "abbrev@npm:1.1.1"
|
resolution: "abbrev@npm:1.1.1"
|
||||||
|
|
@ -1182,6 +1260,7 @@ __metadata:
|
||||||
"@types/plotly.js": ^2.29.2
|
"@types/plotly.js": ^2.29.2
|
||||||
"@typescript-eslint/eslint-plugin": ^5.7.0
|
"@typescript-eslint/eslint-plugin": ^5.7.0
|
||||||
"@typescript-eslint/parser": ^5.7.0
|
"@typescript-eslint/parser": ^5.7.0
|
||||||
|
"@uiw/react-color-colorful": ^2.1.1
|
||||||
eslint: ^8.5.0
|
eslint: ^8.5.0
|
||||||
eslint-config-prettier: ^8.5.0
|
eslint-config-prettier: ^8.5.0
|
||||||
eslint-plugin-prettier: ^4.0.0
|
eslint-plugin-prettier: ^4.0.0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue