@setzkasten-cms/ui 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +533 -325
- package/package.json +2 -2
- package/src/fields/array-field-renderer.tsx +97 -10
- package/src/fields/color-field-renderer.tsx +116 -0
- package/src/fields/date-field-renderer.tsx +39 -0
- package/src/fields/field-renderer.tsx +4 -0
- package/src/styles/admin.css +128 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@setzkasten-cms/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "React-basierte Admin-UI für Setzkasten CMS — Page Builder, Editor, Field Renderers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"react": "^19.1.0",
|
|
44
44
|
"react-dom": "^19.1.0",
|
|
45
45
|
"zustand": "^5.0.0",
|
|
46
|
-
"@setzkasten-cms/core": "0.4.
|
|
46
|
+
"@setzkasten-cms/core": "0.4.6"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/react": "^19.1.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memo, useCallback } from 'react'
|
|
1
|
+
import { memo, useCallback, useState, useRef } from 'react'
|
|
2
2
|
import type { ArrayFieldDef, AnyFieldDef } from '@setzkasten-cms/core'
|
|
3
3
|
import { useField } from '../hooks/use-field'
|
|
4
4
|
import { FieldRenderer, type FieldRendererProps } from './field-renderer'
|
|
@@ -10,8 +10,13 @@ export const ArrayFieldRenderer = memo(function ArrayFieldRenderer({
|
|
|
10
10
|
}: FieldRendererProps) {
|
|
11
11
|
const arrayField = field as ArrayFieldDef
|
|
12
12
|
const { value, setValue } = useField(store, path)
|
|
13
|
+
const [collapsedItems, setCollapsedItems] = useState<Set<number>>(new Set())
|
|
14
|
+
const [dragIndex, setDragIndex] = useState<number | null>(null)
|
|
15
|
+
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null)
|
|
16
|
+
const dragCounter = useRef(0)
|
|
13
17
|
|
|
14
18
|
const items = (value as unknown[]) ?? []
|
|
19
|
+
const isGrid = arrayField.layout === 'grid'
|
|
15
20
|
|
|
16
21
|
const addItem = useCallback(() => {
|
|
17
22
|
const defaultValue = arrayField.itemField.defaultValue ?? (
|
|
@@ -33,6 +38,7 @@ export const ArrayFieldRenderer = memo(function ArrayFieldRenderer({
|
|
|
33
38
|
|
|
34
39
|
const moveItem = useCallback(
|
|
35
40
|
(from: number, to: number) => {
|
|
41
|
+
if (from === to) return
|
|
36
42
|
const newItems = [...items]
|
|
37
43
|
const [moved] = newItems.splice(from, 1)
|
|
38
44
|
newItems.splice(to, 0, moved)
|
|
@@ -41,9 +47,65 @@ export const ArrayFieldRenderer = memo(function ArrayFieldRenderer({
|
|
|
41
47
|
[items, setValue],
|
|
42
48
|
)
|
|
43
49
|
|
|
50
|
+
const toggleCollapse = useCallback((index: number) => {
|
|
51
|
+
setCollapsedItems((prev) => {
|
|
52
|
+
const next = new Set(prev)
|
|
53
|
+
if (next.has(index)) next.delete(index)
|
|
54
|
+
else next.add(index)
|
|
55
|
+
return next
|
|
56
|
+
})
|
|
57
|
+
}, [])
|
|
58
|
+
|
|
59
|
+
// Drag & Drop handlers
|
|
60
|
+
const handleDragStart = useCallback((e: React.DragEvent, index: number) => {
|
|
61
|
+
setDragIndex(index)
|
|
62
|
+
e.dataTransfer.effectAllowed = 'move'
|
|
63
|
+
e.dataTransfer.setData('text/plain', String(index))
|
|
64
|
+
}, [])
|
|
65
|
+
|
|
66
|
+
const handleDragEnter = useCallback((index: number) => {
|
|
67
|
+
dragCounter.current++
|
|
68
|
+
setDragOverIndex(index)
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
const handleDragLeave = useCallback(() => {
|
|
72
|
+
dragCounter.current--
|
|
73
|
+
if (dragCounter.current === 0) {
|
|
74
|
+
setDragOverIndex(null)
|
|
75
|
+
}
|
|
76
|
+
}, [])
|
|
77
|
+
|
|
78
|
+
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
79
|
+
e.preventDefault()
|
|
80
|
+
e.dataTransfer.dropEffect = 'move'
|
|
81
|
+
}, [])
|
|
82
|
+
|
|
83
|
+
const handleDrop = useCallback(
|
|
84
|
+
(e: React.DragEvent, toIndex: number) => {
|
|
85
|
+
e.preventDefault()
|
|
86
|
+
dragCounter.current = 0
|
|
87
|
+
if (dragIndex !== null) {
|
|
88
|
+
moveItem(dragIndex, toIndex)
|
|
89
|
+
}
|
|
90
|
+
setDragIndex(null)
|
|
91
|
+
setDragOverIndex(null)
|
|
92
|
+
},
|
|
93
|
+
[dragIndex, moveItem],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const handleDragEnd = useCallback(() => {
|
|
97
|
+
dragCounter.current = 0
|
|
98
|
+
setDragIndex(null)
|
|
99
|
+
setDragOverIndex(null)
|
|
100
|
+
}, [])
|
|
101
|
+
|
|
44
102
|
const canAdd = arrayField.maxItems === undefined || items.length < arrayField.maxItems
|
|
45
103
|
const canRemove = arrayField.minItems === undefined || items.length > arrayField.minItems
|
|
46
104
|
|
|
105
|
+
const gridStyle = isGrid
|
|
106
|
+
? { gridTemplateColumns: `repeat(${arrayField.gridColumns}, 1fr)` } as React.CSSProperties
|
|
107
|
+
: undefined
|
|
108
|
+
|
|
47
109
|
return (
|
|
48
110
|
<div className="sk-field sk-field--array">
|
|
49
111
|
<div className="sk-array__header">
|
|
@@ -53,27 +115,50 @@ export const ArrayFieldRenderer = memo(function ArrayFieldRenderer({
|
|
|
53
115
|
</label>
|
|
54
116
|
</div>
|
|
55
117
|
|
|
56
|
-
<div
|
|
118
|
+
<div
|
|
119
|
+
className={`sk-array__items ${isGrid ? 'sk-array__items--grid' : ''}`}
|
|
120
|
+
style={gridStyle}
|
|
121
|
+
>
|
|
57
122
|
{items.map((_, index) => {
|
|
58
123
|
const itemLabel = arrayField.itemLabel
|
|
59
124
|
? arrayField.itemLabel(items[index], index)
|
|
60
125
|
: `#${index + 1}`
|
|
61
126
|
|
|
127
|
+
const isCollapsed = arrayField.collapsible && collapsedItems.has(index)
|
|
128
|
+
const isDragging = dragIndex === index
|
|
129
|
+
const isDragOver = dragOverIndex === index && dragIndex !== index
|
|
130
|
+
|
|
62
131
|
return (
|
|
63
|
-
<div
|
|
132
|
+
<div
|
|
133
|
+
key={index}
|
|
134
|
+
className={`sk-array__item ${isDragging ? 'sk-array__item--dragging' : ''} ${isDragOver ? 'sk-array__item--dragover' : ''}`}
|
|
135
|
+
draggable
|
|
136
|
+
onDragStart={(e) => handleDragStart(e, index)}
|
|
137
|
+
onDragEnter={() => handleDragEnter(index)}
|
|
138
|
+
onDragLeave={handleDragLeave}
|
|
139
|
+
onDragOver={handleDragOver}
|
|
140
|
+
onDrop={(e) => handleDrop(e, index)}
|
|
141
|
+
onDragEnd={handleDragEnd}
|
|
142
|
+
>
|
|
64
143
|
<div className="sk-array__item-header">
|
|
144
|
+
<span className="sk-array__item-drag" title="Ziehen zum Sortieren">⠿</span>
|
|
65
145
|
<span className="sk-array__item-label">{itemLabel}</span>
|
|
66
146
|
<div className="sk-array__item-actions">
|
|
67
|
-
{index > 0 && (
|
|
147
|
+
{!isGrid && index > 0 && (
|
|
68
148
|
<button type="button" className="sk-button sk-button--sm" onClick={() => moveItem(index, index - 1)} title="Nach oben">
|
|
69
149
|
↑
|
|
70
150
|
</button>
|
|
71
151
|
)}
|
|
72
|
-
{index < items.length - 1 && (
|
|
152
|
+
{!isGrid && index < items.length - 1 && (
|
|
73
153
|
<button type="button" className="sk-button sk-button--sm" onClick={() => moveItem(index, index + 1)} title="Nach unten">
|
|
74
154
|
↓
|
|
75
155
|
</button>
|
|
76
156
|
)}
|
|
157
|
+
{arrayField.collapsible && (
|
|
158
|
+
<button type="button" className="sk-button sk-button--sm" onClick={() => toggleCollapse(index)} title={isCollapsed ? 'Aufklappen' : 'Zuklappen'}>
|
|
159
|
+
{isCollapsed ? '▸' : '▾'}
|
|
160
|
+
</button>
|
|
161
|
+
)}
|
|
77
162
|
{canRemove && (
|
|
78
163
|
<button type="button" className="sk-button sk-button--sm sk-button--danger" onClick={() => removeItem(index)} title="Entfernen">
|
|
79
164
|
×
|
|
@@ -81,11 +166,13 @@ export const ArrayFieldRenderer = memo(function ArrayFieldRenderer({
|
|
|
81
166
|
)}
|
|
82
167
|
</div>
|
|
83
168
|
</div>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
169
|
+
{!isCollapsed && (
|
|
170
|
+
<FieldRenderer
|
|
171
|
+
field={arrayField.itemField as AnyFieldDef}
|
|
172
|
+
path={[...path, index]}
|
|
173
|
+
store={store}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
89
176
|
</div>
|
|
90
177
|
)
|
|
91
178
|
})}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { memo, useState, useCallback, useRef, useEffect } from 'react'
|
|
2
|
+
import type { ColorFieldDef } from '@setzkasten-cms/core'
|
|
3
|
+
import { useField } from '../hooks/use-field'
|
|
4
|
+
import type { FieldRendererProps } from './field-renderer'
|
|
5
|
+
|
|
6
|
+
export const ColorFieldRenderer = memo(function ColorFieldRenderer({
|
|
7
|
+
field,
|
|
8
|
+
path,
|
|
9
|
+
store,
|
|
10
|
+
}: FieldRendererProps) {
|
|
11
|
+
const colorField = field as ColorFieldDef
|
|
12
|
+
const { value, errors, setValue, touch } = useField(store, path)
|
|
13
|
+
const [showPicker, setShowPicker] = useState(false)
|
|
14
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
15
|
+
|
|
16
|
+
const currentColor = (value as string) || '#000000'
|
|
17
|
+
|
|
18
|
+
// Close picker on outside click
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!showPicker) return
|
|
21
|
+
const handleClick = (e: MouseEvent) => {
|
|
22
|
+
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
|
23
|
+
setShowPicker(false)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
document.addEventListener('mousedown', handleClick)
|
|
27
|
+
return () => document.removeEventListener('mousedown', handleClick)
|
|
28
|
+
}, [showPicker])
|
|
29
|
+
|
|
30
|
+
const handleColorInput = useCallback(
|
|
31
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
32
|
+
setValue(e.target.value)
|
|
33
|
+
},
|
|
34
|
+
[setValue],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const handleHexInput = useCallback(
|
|
38
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
39
|
+
let hex = e.target.value
|
|
40
|
+
if (!hex.startsWith('#')) hex = '#' + hex
|
|
41
|
+
if (/^#[0-9a-fA-F]{6}$/.test(hex)) {
|
|
42
|
+
setValue(hex)
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
[setValue],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const handlePresetClick = useCallback(
|
|
49
|
+
(preset: string) => {
|
|
50
|
+
setValue(preset)
|
|
51
|
+
},
|
|
52
|
+
[setValue],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="sk-field sk-field--color" ref={containerRef}>
|
|
57
|
+
<label className="sk-field__label">{colorField.label}</label>
|
|
58
|
+
{colorField.description && <p className="sk-field__description">{colorField.description}</p>}
|
|
59
|
+
|
|
60
|
+
<div className="sk-color__row">
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
className="sk-color__swatch"
|
|
64
|
+
style={{ backgroundColor: currentColor }}
|
|
65
|
+
onClick={() => setShowPicker(!showPicker)}
|
|
66
|
+
aria-label="Farbe wählen"
|
|
67
|
+
/>
|
|
68
|
+
<input
|
|
69
|
+
className="sk-field__input sk-color__hex"
|
|
70
|
+
type="text"
|
|
71
|
+
value={currentColor}
|
|
72
|
+
onChange={handleHexInput}
|
|
73
|
+
onBlur={touch}
|
|
74
|
+
placeholder="#000000"
|
|
75
|
+
maxLength={7}
|
|
76
|
+
aria-label={colorField.label}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{showPicker && (
|
|
81
|
+
<div className="sk-color__picker">
|
|
82
|
+
<input
|
|
83
|
+
type="color"
|
|
84
|
+
className="sk-color__native"
|
|
85
|
+
value={currentColor}
|
|
86
|
+
onChange={handleColorInput}
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
{colorField.presets.length > 0 && (
|
|
92
|
+
<div className="sk-color__presets">
|
|
93
|
+
{colorField.presets.map((preset) => (
|
|
94
|
+
<button
|
|
95
|
+
key={preset}
|
|
96
|
+
type="button"
|
|
97
|
+
className={`sk-color__preset ${preset === currentColor ? 'sk-color__preset--active' : ''}`}
|
|
98
|
+
style={{ backgroundColor: preset }}
|
|
99
|
+
onClick={() => handlePresetClick(preset)}
|
|
100
|
+
title={preset}
|
|
101
|
+
aria-label={`Farbe ${preset}`}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{errors.length > 0 && (
|
|
108
|
+
<div className="sk-field__errors">
|
|
109
|
+
{errors.map((err, i) => (
|
|
110
|
+
<p key={i} className="sk-field__error">{err}</p>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { memo } from 'react'
|
|
2
|
+
import type { DateFieldDef } from '@setzkasten-cms/core'
|
|
3
|
+
import { useField } from '../hooks/use-field'
|
|
4
|
+
import type { FieldRendererProps } from './field-renderer'
|
|
5
|
+
|
|
6
|
+
export const DateFieldRenderer = memo(function DateFieldRenderer({
|
|
7
|
+
field,
|
|
8
|
+
path,
|
|
9
|
+
store,
|
|
10
|
+
}: FieldRendererProps) {
|
|
11
|
+
const dateField = field as DateFieldDef
|
|
12
|
+
const { value, errors, setValue, touch } = useField(store, path)
|
|
13
|
+
|
|
14
|
+
const inputType = dateField.includeTime ? 'datetime-local' : 'date'
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="sk-field sk-field--date">
|
|
18
|
+
<label className="sk-field__label">{dateField.label}</label>
|
|
19
|
+
{dateField.description && <p className="sk-field__description">{dateField.description}</p>}
|
|
20
|
+
<input
|
|
21
|
+
className="sk-field__input"
|
|
22
|
+
type={inputType}
|
|
23
|
+
value={(value as string) ?? ''}
|
|
24
|
+
onChange={(e) => setValue(e.target.value)}
|
|
25
|
+
onBlur={touch}
|
|
26
|
+
min={dateField.min}
|
|
27
|
+
max={dateField.max}
|
|
28
|
+
aria-label={dateField.label}
|
|
29
|
+
/>
|
|
30
|
+
{errors.length > 0 && (
|
|
31
|
+
<div className="sk-field__errors">
|
|
32
|
+
{errors.map((err, i) => (
|
|
33
|
+
<p key={i} className="sk-field__error">{err}</p>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
})
|
|
@@ -8,6 +8,8 @@ import { IconFieldRenderer } from './icon-field-renderer'
|
|
|
8
8
|
import { ArrayFieldRenderer } from './array-field-renderer'
|
|
9
9
|
import { ObjectFieldRenderer } from './object-field-renderer'
|
|
10
10
|
import { ImageFieldRenderer } from './image-field-renderer'
|
|
11
|
+
import { DateFieldRenderer } from './date-field-renderer'
|
|
12
|
+
import { ColorFieldRenderer } from './color-field-renderer'
|
|
11
13
|
import { OverrideFieldRenderer } from './override-field-renderer'
|
|
12
14
|
import type { createFormStore } from '../stores/form-store'
|
|
13
15
|
|
|
@@ -34,6 +36,8 @@ const rendererMap: Partial<Record<FieldType, ComponentType<FieldRendererProps>>>
|
|
|
34
36
|
array: ArrayFieldRenderer,
|
|
35
37
|
object: ObjectFieldRenderer,
|
|
36
38
|
image: ImageFieldRenderer,
|
|
39
|
+
date: DateFieldRenderer,
|
|
40
|
+
color: ColorFieldRenderer,
|
|
37
41
|
override: OverrideFieldRenderer,
|
|
38
42
|
}
|
|
39
43
|
|
package/src/styles/admin.css
CHANGED
|
@@ -791,11 +791,31 @@
|
|
|
791
791
|
gap: 8px;
|
|
792
792
|
}
|
|
793
793
|
|
|
794
|
+
.sk-array__items--grid {
|
|
795
|
+
display: grid;
|
|
796
|
+
gap: 12px;
|
|
797
|
+
}
|
|
798
|
+
|
|
794
799
|
.sk-array__item {
|
|
795
800
|
background: var(--sk-surface);
|
|
796
801
|
border: 1px solid var(--sk-border);
|
|
797
802
|
border-radius: 8px;
|
|
798
803
|
padding: 12px;
|
|
804
|
+
cursor: grab;
|
|
805
|
+
transition: opacity 0.15s, border-color 0.15s, box-shadow 0.15s;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.sk-array__item:active {
|
|
809
|
+
cursor: grabbing;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.sk-array__item--dragging {
|
|
813
|
+
opacity: 0.4;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.sk-array__item--dragover {
|
|
817
|
+
border-color: var(--sk-accent);
|
|
818
|
+
box-shadow: 0 0 0 2px var(--sk-accent-soft);
|
|
799
819
|
}
|
|
800
820
|
|
|
801
821
|
.sk-array__item-header {
|
|
@@ -805,17 +825,38 @@
|
|
|
805
825
|
margin-bottom: 8px;
|
|
806
826
|
padding-bottom: 8px;
|
|
807
827
|
border-bottom: 1px solid var(--sk-border);
|
|
828
|
+
gap: 8px;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.sk-array__item-drag {
|
|
832
|
+
cursor: grab;
|
|
833
|
+
color: var(--sk-slate);
|
|
834
|
+
font-size: 14px;
|
|
835
|
+
user-select: none;
|
|
836
|
+
line-height: 1;
|
|
837
|
+
opacity: 0.5;
|
|
838
|
+
transition: opacity 0.15s;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.sk-array__item-drag:hover {
|
|
842
|
+
opacity: 1;
|
|
808
843
|
}
|
|
809
844
|
|
|
810
845
|
.sk-array__item-label {
|
|
811
846
|
font-size: 12px;
|
|
812
847
|
font-weight: 600;
|
|
813
848
|
color: var(--sk-slate);
|
|
849
|
+
flex: 1;
|
|
850
|
+
min-width: 0;
|
|
851
|
+
overflow: hidden;
|
|
852
|
+
text-overflow: ellipsis;
|
|
853
|
+
white-space: nowrap;
|
|
814
854
|
}
|
|
815
855
|
|
|
816
856
|
.sk-array__item-actions {
|
|
817
857
|
display: flex;
|
|
818
858
|
gap: 4px;
|
|
859
|
+
flex-shrink: 0;
|
|
819
860
|
}
|
|
820
861
|
|
|
821
862
|
.sk-array__add {
|
|
@@ -831,6 +872,93 @@
|
|
|
831
872
|
border-color: var(--sk-accent);
|
|
832
873
|
}
|
|
833
874
|
|
|
875
|
+
/* ---------------------------------------------------------------------------
|
|
876
|
+
Date Field
|
|
877
|
+
--------------------------------------------------------------------------- */
|
|
878
|
+
|
|
879
|
+
.sk-field--date .sk-field__input {
|
|
880
|
+
font-family: 'DM Sans', system-ui, sans-serif;
|
|
881
|
+
color-scheme: light;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/* ---------------------------------------------------------------------------
|
|
885
|
+
Color Field
|
|
886
|
+
--------------------------------------------------------------------------- */
|
|
887
|
+
|
|
888
|
+
.sk-color__row {
|
|
889
|
+
display: flex;
|
|
890
|
+
align-items: center;
|
|
891
|
+
gap: 8px;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.sk-color__swatch {
|
|
895
|
+
width: 40px;
|
|
896
|
+
height: 40px;
|
|
897
|
+
border-radius: 8px;
|
|
898
|
+
border: 2px solid var(--sk-border);
|
|
899
|
+
cursor: pointer;
|
|
900
|
+
flex-shrink: 0;
|
|
901
|
+
transition: border-color 0.15s;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.sk-color__swatch:hover {
|
|
905
|
+
border-color: var(--sk-accent);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.sk-color__hex {
|
|
909
|
+
flex: 1;
|
|
910
|
+
font-family: 'DM Mono', monospace;
|
|
911
|
+
text-transform: uppercase;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.sk-color__picker {
|
|
915
|
+
margin-top: 8px;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.sk-color__native {
|
|
919
|
+
width: 100%;
|
|
920
|
+
height: 40px;
|
|
921
|
+
border: none;
|
|
922
|
+
border-radius: 8px;
|
|
923
|
+
cursor: pointer;
|
|
924
|
+
padding: 0;
|
|
925
|
+
background: none;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
.sk-color__native::-webkit-color-swatch-wrapper {
|
|
929
|
+
padding: 0;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.sk-color__native::-webkit-color-swatch {
|
|
933
|
+
border: 1px solid var(--sk-border);
|
|
934
|
+
border-radius: 8px;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.sk-color__presets {
|
|
938
|
+
display: flex;
|
|
939
|
+
flex-wrap: wrap;
|
|
940
|
+
gap: 6px;
|
|
941
|
+
margin-top: 8px;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
.sk-color__preset {
|
|
945
|
+
width: 28px;
|
|
946
|
+
height: 28px;
|
|
947
|
+
border-radius: 6px;
|
|
948
|
+
border: 2px solid var(--sk-border);
|
|
949
|
+
cursor: pointer;
|
|
950
|
+
transition: border-color 0.15s, transform 0.1s;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.sk-color__preset:hover {
|
|
954
|
+
transform: scale(1.15);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.sk-color__preset--active {
|
|
958
|
+
border-color: var(--sk-accent);
|
|
959
|
+
box-shadow: 0 0 0 2px var(--sk-accent-soft);
|
|
960
|
+
}
|
|
961
|
+
|
|
834
962
|
/* ---------------------------------------------------------------------------
|
|
835
963
|
Object Field
|
|
836
964
|
--------------------------------------------------------------------------- */
|