@justin_evo/evo-ui 1.2.0 → 1.2.1
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/LICENSE +21 -21
- package/README.md +70 -70
- package/dist/declarations.d.ts +6 -6
- package/package.json +52 -52
- package/src/Alert/Alert.tsx +49 -49
- package/src/AutoComplete/AutoComplete.tsx +810 -810
- package/src/Badge/Badge.tsx +53 -53
- package/src/Breadcrumb/Breadcrumb.tsx +53 -53
- package/src/Button/Button.tsx +125 -125
- package/src/Card/Card.tsx +257 -257
- package/src/Checkbox/Checkbox.tsx +59 -59
- package/src/CommandPalette/CommandPalette.tsx +185 -185
- package/src/Container/Container.tsx +31 -31
- package/src/Divider/Divider.tsx +31 -31
- package/src/Form/Form.tsx +185 -185
- package/src/Grid/Grid.tsx +66 -66
- package/src/ImageCropper/ImageCropper.tsx +911 -911
- package/src/Input/Input.tsx +74 -74
- package/src/Modal/Modal.tsx +77 -77
- package/src/Nav/Nav.tsx +708 -708
- package/src/Notification/Notification.tsx +1503 -1503
- package/src/Pagination/Pagination.tsx +76 -76
- package/src/Radio/Radio.tsx +69 -69
- package/src/RichTextArea/RichTextArea.tsx +886 -886
- package/src/Select/Select.tsx +515 -515
- package/src/Skeleton/Skeleton.tsx +70 -70
- package/src/Stack/Stack.tsx +52 -52
- package/src/Table/Table.tsx +335 -335
- package/src/Tabs/Tabs.tsx +90 -90
- package/src/Theme/ThemeProvider.tsx +253 -253
- package/src/Theme/ThemeToggle.tsx +79 -79
- package/src/Toggle/Toggle.tsx +48 -48
- package/src/Tooltip/Tooltip.tsx +38 -38
- package/src/TopNav/TopNav.tsx +1163 -1163
- package/src/TreeSelect/TreeSelect.tsx +825 -825
- package/src/css/alert.module.scss +93 -93
- package/src/css/autocomplete.module.scss +416 -416
- package/src/css/badge.module.scss +82 -82
- package/src/css/base/_color.scss +159 -159
- package/src/css/base/_theme.scss +237 -237
- package/src/css/base/_variables.scss +161 -161
- package/src/css/breadcrumb.module.scss +50 -50
- package/src/css/button.module.scss +385 -385
- package/src/css/card.module.scss +217 -217
- package/src/css/checkbox.module.scss +123 -123
- package/src/css/commandpalette.module.scss +211 -211
- package/src/css/container.module.scss +18 -18
- package/src/css/divider.module.scss +41 -41
- package/src/css/form.module.scss +245 -245
- package/src/css/imagecropper.module.scss +397 -397
- package/src/css/input.module.scss +89 -89
- package/src/css/modal.module.scss +105 -105
- package/src/css/nav.module.scss +494 -494
- package/src/css/notification.module.scss +691 -691
- package/src/css/pagination.module.scss +63 -63
- package/src/css/radio.module.scss +89 -89
- package/src/css/richtextarea.module.scss +307 -307
- package/src/css/select.module.scss +525 -525
- package/src/css/skeleton.module.scss +30 -30
- package/src/css/table.module.scss +386 -386
- package/src/css/tabs.module.scss +63 -63
- package/src/css/theme-toggle.module.scss +83 -83
- package/src/css/toggle.module.scss +54 -54
- package/src/css/tooltip.module.scss +97 -97
- package/src/css/topnav.module.scss +568 -568
- package/src/css/treeselect.module.scss +558 -558
- package/src/css/utilities/_borders.scss +111 -111
- package/src/css/utilities/_colors.scss +66 -66
- package/src/css/utilities/_effects.scss +216 -216
- package/src/css/utilities/_layout.scss +181 -181
- package/src/css/utilities/_position.scss +75 -75
- package/src/css/utilities/_sizing.scss +138 -138
- package/src/css/utilities/_spacing.scss +99 -99
- package/src/css/utilities/_typography.scss +121 -121
- package/src/css/utilities/index.scss +24 -24
- package/src/declarations.d.ts +6 -6
- package/src/index.ts +60 -60
|
@@ -1,397 +1,397 @@
|
|
|
1
|
-
@use 'base/variables' as *;
|
|
2
|
-
|
|
3
|
-
// =============================================================
|
|
4
|
-
// EvoImageCropper
|
|
5
|
-
// -------------------------------------------------------------
|
|
6
|
-
// A pointer-driven image cropper. The image transforms with
|
|
7
|
-
// CSS (translate + scale + rotate); the crop rectangle is a
|
|
8
|
-
// fixed overlay with eight resize handles. Final pixel work
|
|
9
|
-
// (rendering the crop to a blob) is deferred until requested.
|
|
10
|
-
//
|
|
11
|
-
// Everything responds to viewport width — handles grow on touch,
|
|
12
|
-
// the toolbar reflows on narrow screens, and a tall portrait
|
|
13
|
-
// orientation keeps the canvas usable.
|
|
14
|
-
// =============================================================
|
|
15
|
-
|
|
16
|
-
.root {
|
|
17
|
-
display: flex;
|
|
18
|
-
flex-direction: column;
|
|
19
|
-
gap: 0.75rem;
|
|
20
|
-
font-family: $font-sans;
|
|
21
|
-
color: $color-text-primary;
|
|
22
|
-
min-width: 0;
|
|
23
|
-
width: 100%;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.fullWidth { width: 100%; }
|
|
27
|
-
|
|
28
|
-
.label {
|
|
29
|
-
font-size: $text-sm;
|
|
30
|
-
font-weight: 500;
|
|
31
|
-
color: $color-text-primary;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ---------- Canvas area (image + overlay) ----------
|
|
35
|
-
.stage {
|
|
36
|
-
position: relative;
|
|
37
|
-
width: 100%;
|
|
38
|
-
height: 360px;
|
|
39
|
-
border-radius: $radius-md;
|
|
40
|
-
background-color: $color-surface-sunken;
|
|
41
|
-
border: 1px solid $color-border;
|
|
42
|
-
overflow: hidden;
|
|
43
|
-
touch-action: none;
|
|
44
|
-
user-select: none;
|
|
45
|
-
-webkit-user-select: none;
|
|
46
|
-
-webkit-tap-highlight-color: transparent;
|
|
47
|
-
cursor: grab;
|
|
48
|
-
|
|
49
|
-
&:active { cursor: grabbing; }
|
|
50
|
-
|
|
51
|
-
&.disabled {
|
|
52
|
-
opacity: 0.55;
|
|
53
|
-
pointer-events: none;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Checkerboard background — clearly shows alpha & PNG borders
|
|
58
|
-
.bgChecker {
|
|
59
|
-
background-image:
|
|
60
|
-
linear-gradient(45deg, $color-surface-active 25%, transparent 25%),
|
|
61
|
-
linear-gradient(-45deg, $color-surface-active 25%, transparent 25%),
|
|
62
|
-
linear-gradient(45deg, transparent 75%, $color-surface-active 75%),
|
|
63
|
-
linear-gradient(-45deg, transparent 75%, $color-surface-active 75%);
|
|
64
|
-
background-size: 16px 16px;
|
|
65
|
-
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.imageWrap {
|
|
69
|
-
position: absolute;
|
|
70
|
-
inset: 0;
|
|
71
|
-
display: flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
justify-content: center;
|
|
74
|
-
pointer-events: none;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.image {
|
|
78
|
-
display: block;
|
|
79
|
-
max-width: none;
|
|
80
|
-
pointer-events: none;
|
|
81
|
-
user-select: none;
|
|
82
|
-
-webkit-user-drag: none;
|
|
83
|
-
transform-origin: center center;
|
|
84
|
-
will-change: transform;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ---------- Crop overlay ----------
|
|
88
|
-
// A full-stage element with a giant inset box-shadow that darkens
|
|
89
|
-
// everything *outside* the crop rectangle. The element itself sits
|
|
90
|
-
// at the crop coords and has no fill of its own.
|
|
91
|
-
.overlay {
|
|
92
|
-
position: absolute;
|
|
93
|
-
box-sizing: border-box;
|
|
94
|
-
border: 1px solid rgba(255, 255, 255, 0.92);
|
|
95
|
-
box-shadow:
|
|
96
|
-
0 0 0 9999px rgba(0, 0, 0, 0.55),
|
|
97
|
-
0 0 0 1px rgba(0, 0, 0, 0.25) inset;
|
|
98
|
-
cursor: move;
|
|
99
|
-
touch-action: none;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.overlayCircle {
|
|
103
|
-
border-radius: 50%;
|
|
104
|
-
// Box-shadow can't follow a border-radius cutout, so the circle
|
|
105
|
-
// mode uses a radial gradient on a sibling element instead.
|
|
106
|
-
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25) inset;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.circleMask {
|
|
110
|
-
position: absolute;
|
|
111
|
-
inset: 0;
|
|
112
|
-
pointer-events: none;
|
|
113
|
-
background:
|
|
114
|
-
radial-gradient(circle at var(--mask-cx) var(--mask-cy),
|
|
115
|
-
transparent 0,
|
|
116
|
-
transparent var(--mask-r),
|
|
117
|
-
rgba(0, 0, 0, 0.55) calc(var(--mask-r) + 1px));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ---------- Rule-of-thirds grid ----------
|
|
121
|
-
.grid {
|
|
122
|
-
position: absolute;
|
|
123
|
-
inset: 0;
|
|
124
|
-
pointer-events: none;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.gridLine {
|
|
128
|
-
position: absolute;
|
|
129
|
-
background-color: rgba(255, 255, 255, 0.35);
|
|
130
|
-
|
|
131
|
-
&.h { left: 0; right: 0; height: 1px; }
|
|
132
|
-
&.v { top: 0; bottom: 0; width: 1px; }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ---------- Resize handles ----------
|
|
136
|
-
.handle {
|
|
137
|
-
position: absolute;
|
|
138
|
-
width: 12px;
|
|
139
|
-
height: 12px;
|
|
140
|
-
background-color: #fff;
|
|
141
|
-
border: 1px solid rgba(0, 0, 0, 0.4);
|
|
142
|
-
border-radius: 2px;
|
|
143
|
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
144
|
-
z-index: 2;
|
|
145
|
-
touch-action: none;
|
|
146
|
-
|
|
147
|
-
// Larger tap targets on touch-capable devices
|
|
148
|
-
@media (hover: none) and (pointer: coarse) {
|
|
149
|
-
width: 20px;
|
|
150
|
-
height: 20px;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Corner handles — at the four corners of the crop rect
|
|
155
|
-
.handleTL { top: -7px; left: -7px; cursor: nwse-resize; }
|
|
156
|
-
.handleTR { top: -7px; right: -7px; cursor: nesw-resize; }
|
|
157
|
-
.handleBL { bottom: -7px; left: -7px; cursor: nesw-resize; }
|
|
158
|
-
.handleBR { bottom: -7px; right: -7px; cursor: nwse-resize; }
|
|
159
|
-
|
|
160
|
-
// Edge handles — wider/taller to feel like pull-bars
|
|
161
|
-
.handleT, .handleB {
|
|
162
|
-
left: 50%;
|
|
163
|
-
transform: translateX(-50%);
|
|
164
|
-
width: 24px;
|
|
165
|
-
height: 6px;
|
|
166
|
-
cursor: ns-resize;
|
|
167
|
-
|
|
168
|
-
@media (hover: none) and (pointer: coarse) {
|
|
169
|
-
width: 36px;
|
|
170
|
-
height: 10px;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
.handleL, .handleR {
|
|
174
|
-
top: 50%;
|
|
175
|
-
transform: translateY(-50%);
|
|
176
|
-
width: 6px;
|
|
177
|
-
height: 24px;
|
|
178
|
-
cursor: ew-resize;
|
|
179
|
-
|
|
180
|
-
@media (hover: none) and (pointer: coarse) {
|
|
181
|
-
width: 10px;
|
|
182
|
-
height: 36px;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
.handleT { top: -3px; }
|
|
186
|
-
.handleB { bottom: -3px; }
|
|
187
|
-
.handleL { left: -3px; }
|
|
188
|
-
.handleR { right: -3px; }
|
|
189
|
-
|
|
190
|
-
// In circular mode, edge handles look odd; hide them and keep corners
|
|
191
|
-
.overlay.overlayCircle {
|
|
192
|
-
.handleT, .handleB, .handleL, .handleR { display: none; }
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ---------- Controls toolbar ----------
|
|
196
|
-
.controls {
|
|
197
|
-
display: flex;
|
|
198
|
-
flex-wrap: wrap;
|
|
199
|
-
align-items: center;
|
|
200
|
-
gap: 0.5rem 0.75rem;
|
|
201
|
-
padding: 0.625rem 0.75rem;
|
|
202
|
-
background-color: $color-surface;
|
|
203
|
-
border: 1px solid $color-border;
|
|
204
|
-
border-radius: $radius-sm;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.controlGroup {
|
|
208
|
-
display: flex;
|
|
209
|
-
align-items: center;
|
|
210
|
-
gap: 0.375rem;
|
|
211
|
-
min-width: 0;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.controlLabel {
|
|
215
|
-
font-size: $text-xs;
|
|
216
|
-
color: $color-text-muted;
|
|
217
|
-
font-weight: 500;
|
|
218
|
-
letter-spacing: 0.02em;
|
|
219
|
-
text-transform: uppercase;
|
|
220
|
-
white-space: nowrap;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.iconBtn {
|
|
224
|
-
display: inline-flex;
|
|
225
|
-
align-items: center;
|
|
226
|
-
justify-content: center;
|
|
227
|
-
width: 32px;
|
|
228
|
-
height: 32px;
|
|
229
|
-
padding: 0;
|
|
230
|
-
background: transparent;
|
|
231
|
-
border: 1px solid $color-border;
|
|
232
|
-
border-radius: $evo-border-radius-sm;
|
|
233
|
-
color: $color-text-secondary;
|
|
234
|
-
cursor: pointer;
|
|
235
|
-
transition: background-color $transition-fast, color $transition-fast, border-color $transition-fast;
|
|
236
|
-
-webkit-tap-highlight-color: transparent;
|
|
237
|
-
|
|
238
|
-
&:hover:not(:disabled) {
|
|
239
|
-
background-color: $color-surface-hover;
|
|
240
|
-
color: $color-text-primary;
|
|
241
|
-
border-color: $color-border-strong;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
&:active:not(:disabled) { background-color: $color-surface-active; }
|
|
245
|
-
|
|
246
|
-
&:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
247
|
-
|
|
248
|
-
&:focus-visible {
|
|
249
|
-
outline: none;
|
|
250
|
-
border-color: $evo-primary-color;
|
|
251
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, $evo-primary-color 22%, transparent);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.zoomSlider {
|
|
256
|
-
appearance: none;
|
|
257
|
-
-webkit-appearance: none;
|
|
258
|
-
flex: 1 1 120px;
|
|
259
|
-
min-width: 80px;
|
|
260
|
-
max-width: 220px;
|
|
261
|
-
height: 4px;
|
|
262
|
-
background-color: $color-surface-active;
|
|
263
|
-
border-radius: $radius-full;
|
|
264
|
-
cursor: pointer;
|
|
265
|
-
|
|
266
|
-
&::-webkit-slider-thumb {
|
|
267
|
-
appearance: none;
|
|
268
|
-
-webkit-appearance: none;
|
|
269
|
-
width: 16px;
|
|
270
|
-
height: 16px;
|
|
271
|
-
background-color: $evo-primary-color;
|
|
272
|
-
border-radius: 50%;
|
|
273
|
-
cursor: pointer;
|
|
274
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
&::-moz-range-thumb {
|
|
278
|
-
width: 16px;
|
|
279
|
-
height: 16px;
|
|
280
|
-
background-color: $evo-primary-color;
|
|
281
|
-
border: none;
|
|
282
|
-
border-radius: 50%;
|
|
283
|
-
cursor: pointer;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
&:focus-visible {
|
|
287
|
-
outline: none;
|
|
288
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, $evo-primary-color 22%, transparent);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ---------- Aspect ratio preset chips ----------
|
|
293
|
-
.ratioRow {
|
|
294
|
-
display: flex;
|
|
295
|
-
flex-wrap: wrap;
|
|
296
|
-
gap: 0.375rem;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.ratioChip {
|
|
300
|
-
display: inline-flex;
|
|
301
|
-
align-items: center;
|
|
302
|
-
padding: 0.25rem 0.625rem;
|
|
303
|
-
background-color: $color-surface;
|
|
304
|
-
border: 1px solid $color-border;
|
|
305
|
-
border-radius: $radius-full;
|
|
306
|
-
font-size: $text-xs;
|
|
307
|
-
font-weight: 500;
|
|
308
|
-
color: $color-text-secondary;
|
|
309
|
-
cursor: pointer;
|
|
310
|
-
transition: background-color $transition-fast, color $transition-fast, border-color $transition-fast;
|
|
311
|
-
-webkit-tap-highlight-color: transparent;
|
|
312
|
-
|
|
313
|
-
&:hover:not(:disabled) {
|
|
314
|
-
background-color: $color-surface-hover;
|
|
315
|
-
color: $color-text-primary;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
&.active {
|
|
319
|
-
background-color: $evo-primary-soft;
|
|
320
|
-
border-color: $evo-primary-color;
|
|
321
|
-
color: $evo-primary-color;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
&:focus-visible {
|
|
325
|
-
outline: none;
|
|
326
|
-
border-color: $evo-primary-color;
|
|
327
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, $evo-primary-color 22%, transparent);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
&:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.divider {
|
|
334
|
-
width: 1px;
|
|
335
|
-
height: 20px;
|
|
336
|
-
background-color: $color-border;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
.helper {
|
|
340
|
-
font-size: $text-xs;
|
|
341
|
-
color: $color-text-muted;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// ---------- Loading state ----------
|
|
345
|
-
.placeholder {
|
|
346
|
-
position: absolute;
|
|
347
|
-
inset: 0;
|
|
348
|
-
display: flex;
|
|
349
|
-
flex-direction: column;
|
|
350
|
-
align-items: center;
|
|
351
|
-
justify-content: center;
|
|
352
|
-
gap: 0.5rem;
|
|
353
|
-
color: $color-text-muted;
|
|
354
|
-
font-size: $text-sm;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
.spinner {
|
|
358
|
-
width: 20px;
|
|
359
|
-
height: 20px;
|
|
360
|
-
border: 2px solid $color-border;
|
|
361
|
-
border-top-color: $evo-primary-color;
|
|
362
|
-
border-radius: 50%;
|
|
363
|
-
animation: evoCropperSpin 700ms linear infinite;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
@keyframes evoCropperSpin {
|
|
367
|
-
to { transform: rotate(360deg); }
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// ---------- Responsive ----------
|
|
371
|
-
@media (max-width: 640px) {
|
|
372
|
-
.stage { height: 300px; }
|
|
373
|
-
|
|
374
|
-
.controls {
|
|
375
|
-
padding: 0.5rem;
|
|
376
|
-
gap: 0.5rem;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// On narrow screens the divider becomes a full-width row break
|
|
380
|
-
.divider {
|
|
381
|
-
width: 100%;
|
|
382
|
-
height: 0;
|
|
383
|
-
background: transparent;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.controlGroup { flex: 1 1 auto; justify-content: flex-start; }
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
@media (max-width: 420px) {
|
|
390
|
-
.stage { height: 260px; }
|
|
391
|
-
.controlLabel { display: none; }
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
@media (prefers-reduced-motion: reduce) {
|
|
395
|
-
.image, .overlay, .iconBtn, .ratioChip { transition: none !important; }
|
|
396
|
-
.spinner { animation: none; }
|
|
397
|
-
}
|
|
1
|
+
@use 'base/variables' as *;
|
|
2
|
+
|
|
3
|
+
// =============================================================
|
|
4
|
+
// EvoImageCropper
|
|
5
|
+
// -------------------------------------------------------------
|
|
6
|
+
// A pointer-driven image cropper. The image transforms with
|
|
7
|
+
// CSS (translate + scale + rotate); the crop rectangle is a
|
|
8
|
+
// fixed overlay with eight resize handles. Final pixel work
|
|
9
|
+
// (rendering the crop to a blob) is deferred until requested.
|
|
10
|
+
//
|
|
11
|
+
// Everything responds to viewport width — handles grow on touch,
|
|
12
|
+
// the toolbar reflows on narrow screens, and a tall portrait
|
|
13
|
+
// orientation keeps the canvas usable.
|
|
14
|
+
// =============================================================
|
|
15
|
+
|
|
16
|
+
.root {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 0.75rem;
|
|
20
|
+
font-family: $font-sans;
|
|
21
|
+
color: $color-text-primary;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.fullWidth { width: 100%; }
|
|
27
|
+
|
|
28
|
+
.label {
|
|
29
|
+
font-size: $text-sm;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
color: $color-text-primary;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------- Canvas area (image + overlay) ----------
|
|
35
|
+
.stage {
|
|
36
|
+
position: relative;
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 360px;
|
|
39
|
+
border-radius: $radius-md;
|
|
40
|
+
background-color: $color-surface-sunken;
|
|
41
|
+
border: 1px solid $color-border;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
touch-action: none;
|
|
44
|
+
user-select: none;
|
|
45
|
+
-webkit-user-select: none;
|
|
46
|
+
-webkit-tap-highlight-color: transparent;
|
|
47
|
+
cursor: grab;
|
|
48
|
+
|
|
49
|
+
&:active { cursor: grabbing; }
|
|
50
|
+
|
|
51
|
+
&.disabled {
|
|
52
|
+
opacity: 0.55;
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Checkerboard background — clearly shows alpha & PNG borders
|
|
58
|
+
.bgChecker {
|
|
59
|
+
background-image:
|
|
60
|
+
linear-gradient(45deg, $color-surface-active 25%, transparent 25%),
|
|
61
|
+
linear-gradient(-45deg, $color-surface-active 25%, transparent 25%),
|
|
62
|
+
linear-gradient(45deg, transparent 75%, $color-surface-active 75%),
|
|
63
|
+
linear-gradient(-45deg, transparent 75%, $color-surface-active 75%);
|
|
64
|
+
background-size: 16px 16px;
|
|
65
|
+
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.imageWrap {
|
|
69
|
+
position: absolute;
|
|
70
|
+
inset: 0;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.image {
|
|
78
|
+
display: block;
|
|
79
|
+
max-width: none;
|
|
80
|
+
pointer-events: none;
|
|
81
|
+
user-select: none;
|
|
82
|
+
-webkit-user-drag: none;
|
|
83
|
+
transform-origin: center center;
|
|
84
|
+
will-change: transform;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------- Crop overlay ----------
|
|
88
|
+
// A full-stage element with a giant inset box-shadow that darkens
|
|
89
|
+
// everything *outside* the crop rectangle. The element itself sits
|
|
90
|
+
// at the crop coords and has no fill of its own.
|
|
91
|
+
.overlay {
|
|
92
|
+
position: absolute;
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
border: 1px solid rgba(255, 255, 255, 0.92);
|
|
95
|
+
box-shadow:
|
|
96
|
+
0 0 0 9999px rgba(0, 0, 0, 0.55),
|
|
97
|
+
0 0 0 1px rgba(0, 0, 0, 0.25) inset;
|
|
98
|
+
cursor: move;
|
|
99
|
+
touch-action: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.overlayCircle {
|
|
103
|
+
border-radius: 50%;
|
|
104
|
+
// Box-shadow can't follow a border-radius cutout, so the circle
|
|
105
|
+
// mode uses a radial gradient on a sibling element instead.
|
|
106
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25) inset;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.circleMask {
|
|
110
|
+
position: absolute;
|
|
111
|
+
inset: 0;
|
|
112
|
+
pointer-events: none;
|
|
113
|
+
background:
|
|
114
|
+
radial-gradient(circle at var(--mask-cx) var(--mask-cy),
|
|
115
|
+
transparent 0,
|
|
116
|
+
transparent var(--mask-r),
|
|
117
|
+
rgba(0, 0, 0, 0.55) calc(var(--mask-r) + 1px));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------- Rule-of-thirds grid ----------
|
|
121
|
+
.grid {
|
|
122
|
+
position: absolute;
|
|
123
|
+
inset: 0;
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.gridLine {
|
|
128
|
+
position: absolute;
|
|
129
|
+
background-color: rgba(255, 255, 255, 0.35);
|
|
130
|
+
|
|
131
|
+
&.h { left: 0; right: 0; height: 1px; }
|
|
132
|
+
&.v { top: 0; bottom: 0; width: 1px; }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------- Resize handles ----------
|
|
136
|
+
.handle {
|
|
137
|
+
position: absolute;
|
|
138
|
+
width: 12px;
|
|
139
|
+
height: 12px;
|
|
140
|
+
background-color: #fff;
|
|
141
|
+
border: 1px solid rgba(0, 0, 0, 0.4);
|
|
142
|
+
border-radius: 2px;
|
|
143
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
144
|
+
z-index: 2;
|
|
145
|
+
touch-action: none;
|
|
146
|
+
|
|
147
|
+
// Larger tap targets on touch-capable devices
|
|
148
|
+
@media (hover: none) and (pointer: coarse) {
|
|
149
|
+
width: 20px;
|
|
150
|
+
height: 20px;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Corner handles — at the four corners of the crop rect
|
|
155
|
+
.handleTL { top: -7px; left: -7px; cursor: nwse-resize; }
|
|
156
|
+
.handleTR { top: -7px; right: -7px; cursor: nesw-resize; }
|
|
157
|
+
.handleBL { bottom: -7px; left: -7px; cursor: nesw-resize; }
|
|
158
|
+
.handleBR { bottom: -7px; right: -7px; cursor: nwse-resize; }
|
|
159
|
+
|
|
160
|
+
// Edge handles — wider/taller to feel like pull-bars
|
|
161
|
+
.handleT, .handleB {
|
|
162
|
+
left: 50%;
|
|
163
|
+
transform: translateX(-50%);
|
|
164
|
+
width: 24px;
|
|
165
|
+
height: 6px;
|
|
166
|
+
cursor: ns-resize;
|
|
167
|
+
|
|
168
|
+
@media (hover: none) and (pointer: coarse) {
|
|
169
|
+
width: 36px;
|
|
170
|
+
height: 10px;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
.handleL, .handleR {
|
|
174
|
+
top: 50%;
|
|
175
|
+
transform: translateY(-50%);
|
|
176
|
+
width: 6px;
|
|
177
|
+
height: 24px;
|
|
178
|
+
cursor: ew-resize;
|
|
179
|
+
|
|
180
|
+
@media (hover: none) and (pointer: coarse) {
|
|
181
|
+
width: 10px;
|
|
182
|
+
height: 36px;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
.handleT { top: -3px; }
|
|
186
|
+
.handleB { bottom: -3px; }
|
|
187
|
+
.handleL { left: -3px; }
|
|
188
|
+
.handleR { right: -3px; }
|
|
189
|
+
|
|
190
|
+
// In circular mode, edge handles look odd; hide them and keep corners
|
|
191
|
+
.overlay.overlayCircle {
|
|
192
|
+
.handleT, .handleB, .handleL, .handleR { display: none; }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------- Controls toolbar ----------
|
|
196
|
+
.controls {
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-wrap: wrap;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: 0.5rem 0.75rem;
|
|
201
|
+
padding: 0.625rem 0.75rem;
|
|
202
|
+
background-color: $color-surface;
|
|
203
|
+
border: 1px solid $color-border;
|
|
204
|
+
border-radius: $radius-sm;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.controlGroup {
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
gap: 0.375rem;
|
|
211
|
+
min-width: 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.controlLabel {
|
|
215
|
+
font-size: $text-xs;
|
|
216
|
+
color: $color-text-muted;
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
letter-spacing: 0.02em;
|
|
219
|
+
text-transform: uppercase;
|
|
220
|
+
white-space: nowrap;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.iconBtn {
|
|
224
|
+
display: inline-flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
width: 32px;
|
|
228
|
+
height: 32px;
|
|
229
|
+
padding: 0;
|
|
230
|
+
background: transparent;
|
|
231
|
+
border: 1px solid $color-border;
|
|
232
|
+
border-radius: $evo-border-radius-sm;
|
|
233
|
+
color: $color-text-secondary;
|
|
234
|
+
cursor: pointer;
|
|
235
|
+
transition: background-color $transition-fast, color $transition-fast, border-color $transition-fast;
|
|
236
|
+
-webkit-tap-highlight-color: transparent;
|
|
237
|
+
|
|
238
|
+
&:hover:not(:disabled) {
|
|
239
|
+
background-color: $color-surface-hover;
|
|
240
|
+
color: $color-text-primary;
|
|
241
|
+
border-color: $color-border-strong;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
&:active:not(:disabled) { background-color: $color-surface-active; }
|
|
245
|
+
|
|
246
|
+
&:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
247
|
+
|
|
248
|
+
&:focus-visible {
|
|
249
|
+
outline: none;
|
|
250
|
+
border-color: $evo-primary-color;
|
|
251
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, $evo-primary-color 22%, transparent);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.zoomSlider {
|
|
256
|
+
appearance: none;
|
|
257
|
+
-webkit-appearance: none;
|
|
258
|
+
flex: 1 1 120px;
|
|
259
|
+
min-width: 80px;
|
|
260
|
+
max-width: 220px;
|
|
261
|
+
height: 4px;
|
|
262
|
+
background-color: $color-surface-active;
|
|
263
|
+
border-radius: $radius-full;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
|
|
266
|
+
&::-webkit-slider-thumb {
|
|
267
|
+
appearance: none;
|
|
268
|
+
-webkit-appearance: none;
|
|
269
|
+
width: 16px;
|
|
270
|
+
height: 16px;
|
|
271
|
+
background-color: $evo-primary-color;
|
|
272
|
+
border-radius: 50%;
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
&::-moz-range-thumb {
|
|
278
|
+
width: 16px;
|
|
279
|
+
height: 16px;
|
|
280
|
+
background-color: $evo-primary-color;
|
|
281
|
+
border: none;
|
|
282
|
+
border-radius: 50%;
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
&:focus-visible {
|
|
287
|
+
outline: none;
|
|
288
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, $evo-primary-color 22%, transparent);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ---------- Aspect ratio preset chips ----------
|
|
293
|
+
.ratioRow {
|
|
294
|
+
display: flex;
|
|
295
|
+
flex-wrap: wrap;
|
|
296
|
+
gap: 0.375rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.ratioChip {
|
|
300
|
+
display: inline-flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
padding: 0.25rem 0.625rem;
|
|
303
|
+
background-color: $color-surface;
|
|
304
|
+
border: 1px solid $color-border;
|
|
305
|
+
border-radius: $radius-full;
|
|
306
|
+
font-size: $text-xs;
|
|
307
|
+
font-weight: 500;
|
|
308
|
+
color: $color-text-secondary;
|
|
309
|
+
cursor: pointer;
|
|
310
|
+
transition: background-color $transition-fast, color $transition-fast, border-color $transition-fast;
|
|
311
|
+
-webkit-tap-highlight-color: transparent;
|
|
312
|
+
|
|
313
|
+
&:hover:not(:disabled) {
|
|
314
|
+
background-color: $color-surface-hover;
|
|
315
|
+
color: $color-text-primary;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
&.active {
|
|
319
|
+
background-color: $evo-primary-soft;
|
|
320
|
+
border-color: $evo-primary-color;
|
|
321
|
+
color: $evo-primary-color;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
&:focus-visible {
|
|
325
|
+
outline: none;
|
|
326
|
+
border-color: $evo-primary-color;
|
|
327
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, $evo-primary-color 22%, transparent);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
&:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.divider {
|
|
334
|
+
width: 1px;
|
|
335
|
+
height: 20px;
|
|
336
|
+
background-color: $color-border;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.helper {
|
|
340
|
+
font-size: $text-xs;
|
|
341
|
+
color: $color-text-muted;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---------- Loading state ----------
|
|
345
|
+
.placeholder {
|
|
346
|
+
position: absolute;
|
|
347
|
+
inset: 0;
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-direction: column;
|
|
350
|
+
align-items: center;
|
|
351
|
+
justify-content: center;
|
|
352
|
+
gap: 0.5rem;
|
|
353
|
+
color: $color-text-muted;
|
|
354
|
+
font-size: $text-sm;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.spinner {
|
|
358
|
+
width: 20px;
|
|
359
|
+
height: 20px;
|
|
360
|
+
border: 2px solid $color-border;
|
|
361
|
+
border-top-color: $evo-primary-color;
|
|
362
|
+
border-radius: 50%;
|
|
363
|
+
animation: evoCropperSpin 700ms linear infinite;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@keyframes evoCropperSpin {
|
|
367
|
+
to { transform: rotate(360deg); }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ---------- Responsive ----------
|
|
371
|
+
@media (max-width: 640px) {
|
|
372
|
+
.stage { height: 300px; }
|
|
373
|
+
|
|
374
|
+
.controls {
|
|
375
|
+
padding: 0.5rem;
|
|
376
|
+
gap: 0.5rem;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// On narrow screens the divider becomes a full-width row break
|
|
380
|
+
.divider {
|
|
381
|
+
width: 100%;
|
|
382
|
+
height: 0;
|
|
383
|
+
background: transparent;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.controlGroup { flex: 1 1 auto; justify-content: flex-start; }
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@media (max-width: 420px) {
|
|
390
|
+
.stage { height: 260px; }
|
|
391
|
+
.controlLabel { display: none; }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
@media (prefers-reduced-motion: reduce) {
|
|
395
|
+
.image, .overlay, .iconBtn, .ratioChip { transition: none !important; }
|
|
396
|
+
.spinner { animation: none; }
|
|
397
|
+
}
|