@luxfi/ui 7.4.10 → 7.4.12
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/accordion.cjs +107 -166
- package/dist/accordion.d.cts +18 -51
- package/dist/accordion.d.ts +18 -51
- package/dist/accordion.js +109 -166
- package/dist/alert.cjs +68 -46
- package/dist/alert.js +69 -46
- package/dist/avatar.cjs +49 -46
- package/dist/avatar.js +52 -48
- package/dist/badge.cjs +82 -49
- package/dist/badge.js +83 -50
- package/dist/checkbox.cjs +42 -75
- package/dist/checkbox.js +43 -76
- package/dist/chrome.cjs +196 -386
- package/dist/chrome.js +198 -386
- package/dist/close-button.cjs +3 -7
- package/dist/close-button.js +3 -7
- package/dist/collapsible.cjs +35 -26
- package/dist/collapsible.d.cts +2 -1
- package/dist/collapsible.d.ts +2 -1
- package/dist/collapsible.js +34 -24
- package/dist/dialog.cjs +3 -7
- package/dist/dialog.d.cts +1 -1
- package/dist/dialog.d.ts +1 -1
- package/dist/dialog.js +3 -7
- package/dist/drawer.cjs +95 -122
- package/dist/drawer.d.cts +43 -10
- package/dist/drawer.d.ts +43 -10
- package/dist/drawer.js +97 -122
- package/dist/empty-state.cjs +13 -13
- package/dist/empty-state.js +13 -13
- package/dist/expiration-selector.cjs +58 -63
- package/dist/expiration-selector.js +58 -63
- package/dist/field.cjs +60 -109
- package/dist/field.d.cts +0 -1
- package/dist/field.d.ts +0 -1
- package/dist/field.js +61 -109
- package/dist/greeks-display.cjs +48 -39
- package/dist/greeks-display.js +48 -39
- package/dist/heading.cjs +10 -17
- package/dist/heading.d.cts +0 -2
- package/dist/heading.d.ts +0 -2
- package/dist/heading.js +10 -17
- package/dist/icon-button.cjs +82 -82
- package/dist/icon-button.d.cts +16 -12
- package/dist/icon-button.d.ts +16 -12
- package/dist/icon-button.js +82 -82
- package/dist/index.cjs +1812 -2105
- package/dist/index.d.cts +1 -5
- package/dist/index.d.ts +1 -5
- package/dist/index.js +1814 -2100
- package/dist/input-group.cjs +11 -12
- package/dist/input-group.js +11 -12
- package/dist/input.cjs +27 -27
- package/dist/input.d.cts +21 -1
- package/dist/input.d.ts +21 -1
- package/dist/input.js +25 -28
- package/dist/menu.cjs +160 -271
- package/dist/menu.d.cts +41 -57
- package/dist/menu.d.ts +41 -57
- package/dist/menu.js +161 -251
- package/dist/option-chain.cjs +89 -67
- package/dist/option-chain.js +89 -67
- package/dist/option-position.cjs +88 -85
- package/dist/option-position.js +88 -85
- package/dist/pnl-diagram.cjs +20 -20
- package/dist/pnl-diagram.js +20 -20
- package/dist/popover.cjs +4 -8
- package/dist/popover.js +4 -8
- package/dist/progress.cjs +15 -22
- package/dist/progress.d.cts +6 -6
- package/dist/progress.d.ts +6 -6
- package/dist/progress.js +15 -22
- package/dist/radio.cjs +35 -67
- package/dist/radio.d.cts +18 -18
- package/dist/radio.d.ts +18 -18
- package/dist/radio.js +36 -68
- package/dist/select.cjs +40 -39
- package/dist/select.d.cts +35 -4
- package/dist/select.d.ts +35 -4
- package/dist/select.js +40 -38
- package/dist/separator.cjs +8 -33
- package/dist/separator.js +8 -33
- package/dist/slider.cjs +33 -49
- package/dist/slider.js +34 -50
- package/dist/switch.cjs +48 -55
- package/dist/switch.js +49 -56
- package/dist/tabs.cjs +108 -197
- package/dist/tabs.d.cts +16 -60
- package/dist/tabs.d.ts +16 -60
- package/dist/tabs.js +109 -196
- package/dist/tag.cjs +112 -69
- package/dist/tag.js +113 -69
- package/dist/textarea.cjs +56 -27
- package/dist/textarea.js +57 -28
- package/dist/toaster.cjs +97 -79
- package/dist/toaster.d.cts +7 -8
- package/dist/toaster.d.ts +7 -8
- package/dist/toaster.js +78 -80
- package/dist/tooltip.cjs +28 -17
- package/dist/tooltip.js +30 -18
- package/package.json +2 -16
- package/src/accordion.tsx +173 -227
- package/src/alert.tsx +78 -45
- package/src/avatar.tsx +54 -37
- package/src/badge.tsx +65 -44
- package/src/checkbox.tsx +70 -89
- package/src/close-button.tsx +3 -8
- package/src/collapsible.tsx +55 -52
- package/src/drawer.tsx +123 -115
- package/src/empty-state.tsx +21 -17
- package/src/expiration-selector.tsx +84 -66
- package/src/field.tsx +68 -124
- package/src/greeks-display.tsx +59 -51
- package/src/heading.tsx +22 -26
- package/src/icon-button.tsx +134 -141
- package/src/ink.tsx +37 -0
- package/src/input-group.tsx +59 -36
- package/src/input.tsx +41 -32
- package/src/menu.tsx +224 -397
- package/src/option-chain.tsx +154 -104
- package/src/option-position.tsx +136 -114
- package/src/pin-input.tsx +37 -29
- package/src/pnl-diagram.tsx +36 -28
- package/src/popover.tsx +1 -1
- package/src/progress.tsx +17 -21
- package/src/radio.tsx +56 -75
- package/src/select.tsx +80 -59
- package/src/separator.tsx +13 -34
- package/src/slider.tsx +43 -44
- package/src/strategy-builder.tsx +245 -156
- package/src/switch.tsx +63 -64
- package/src/tabs.tsx +166 -296
- package/src/tag.tsx +89 -51
- package/src/textarea.tsx +19 -29
- package/src/toaster.tsx +118 -86
- package/src/tooltip.tsx +21 -13
- package/tokens.css +79 -0
package/src/select.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { Select as GuiSelect } from '@hanzo/gui';
|
|
4
4
|
import { useDebounce } from '@uidotdev/usehooks';
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
|
|
@@ -229,15 +229,14 @@ export const SelectRoot = React.forwardRef<HTMLDivElement, SelectRootProps>(
|
|
|
229
229
|
|
|
230
230
|
return (
|
|
231
231
|
<SelectInternalContext.Provider value={ ctxValue }>
|
|
232
|
-
<
|
|
232
|
+
<GuiSelect
|
|
233
233
|
value={ currentValue[0] ?? '' }
|
|
234
234
|
defaultValue={ defaultValueArr?.[0] }
|
|
235
235
|
onValueChange={ handleRadixValueChange }
|
|
236
236
|
open={ isOpen }
|
|
237
237
|
onOpenChange={ handleOpenChange }
|
|
238
|
-
|
|
238
|
+
disablePreventBodyScroll
|
|
239
239
|
name={ name }
|
|
240
|
-
required={ required }
|
|
241
240
|
>
|
|
242
241
|
<div
|
|
243
242
|
ref={ ref }
|
|
@@ -250,7 +249,7 @@ export const SelectRoot = React.forwardRef<HTMLDivElement, SelectRootProps>(
|
|
|
250
249
|
>
|
|
251
250
|
{ children }
|
|
252
251
|
</div>
|
|
253
|
-
</
|
|
252
|
+
</GuiSelect>
|
|
254
253
|
</SelectInternalContext.Provider>
|
|
255
254
|
);
|
|
256
255
|
},
|
|
@@ -263,7 +262,7 @@ export const SelectRoot = React.forwardRef<HTMLDivElement, SelectRootProps>(
|
|
|
263
262
|
export interface SelectControlProps {
|
|
264
263
|
children?: React.ReactNode;
|
|
265
264
|
noIndicator?: boolean;
|
|
266
|
-
triggerProps?: React.
|
|
265
|
+
triggerProps?: React.ComponentProps<typeof GuiSelect.Trigger> & {
|
|
267
266
|
asChild?: boolean;
|
|
268
267
|
px?: string | number;
|
|
269
268
|
className?: string;
|
|
@@ -286,9 +285,10 @@ export const SelectControl = React.forwardRef<HTMLButtonElement, SelectControlPr
|
|
|
286
285
|
const { asChild, px: _px, className: triggerClassName, ...radixTriggerProps } = triggerProps ?? {};
|
|
287
286
|
|
|
288
287
|
const trigger = (
|
|
289
|
-
<
|
|
290
|
-
ref={ ref }
|
|
288
|
+
<GuiSelect.Trigger
|
|
289
|
+
ref={ ref as never }
|
|
291
290
|
asChild={ asChild }
|
|
291
|
+
unstyled
|
|
292
292
|
className={ cn(
|
|
293
293
|
'group peer inline-flex items-center gap-2 cursor-pointer',
|
|
294
294
|
'rounded-lg text-sm transition-colors outline-none',
|
|
@@ -314,7 +314,7 @@ export const SelectControl = React.forwardRef<HTMLButtonElement, SelectControlPr
|
|
|
314
314
|
) }
|
|
315
315
|
</>
|
|
316
316
|
) }
|
|
317
|
-
</
|
|
317
|
+
</GuiSelect.Trigger>
|
|
318
318
|
);
|
|
319
319
|
|
|
320
320
|
if (loading) {
|
|
@@ -376,39 +376,33 @@ export interface SelectContentProps {
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
export const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(
|
|
379
|
-
function SelectContent(props,
|
|
380
|
-
|
|
379
|
+
function SelectContent(props, _ref) {
|
|
380
|
+
// `portalled` and `portalRef` stay in the type because callers pass them,
|
|
381
|
+
// but gui's Select.Content owns its own layering — it mounts through the
|
|
382
|
+
// engine's portal and z-index stack whatever we say. There is no second
|
|
383
|
+
// way to place it, so there is nothing here to switch on.
|
|
384
|
+
const { portalled: _portalled, portalRef: _portalRef, children, className, ...rest } = props;
|
|
381
385
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
position="popper"
|
|
386
|
-
sideOffset={ 4 }
|
|
387
|
-
className={ cn(
|
|
388
|
-
'z-50 min-w-[8rem] overflow-hidden rounded-lg',
|
|
389
|
-
'bg-[var(--color-popover-bg)] shadow-[var(--shadow-popover)]',
|
|
390
|
-
'border border-[var(--color-border-divider)]',
|
|
391
|
-
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
392
|
-
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
393
|
-
className,
|
|
394
|
-
) }
|
|
395
|
-
{ ...rest }
|
|
396
|
-
>
|
|
397
|
-
<RadixSelect.Viewport className="p-1">
|
|
398
|
-
{ children }
|
|
399
|
-
</RadixSelect.Viewport>
|
|
400
|
-
</RadixSelect.Content>
|
|
401
|
-
);
|
|
386
|
+
// One counter per render, walked over the rows below.
|
|
387
|
+
let n = 0;
|
|
388
|
+
const counter = () => n++;
|
|
402
389
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
390
|
+
return (
|
|
391
|
+
// Content is the focus scope — it takes no style props at all. The
|
|
392
|
+
// Viewport is the box you can see, so the surface is declared there.
|
|
393
|
+
<GuiSelect.Content { ...rest }>
|
|
394
|
+
<GuiSelect.Viewport
|
|
395
|
+
className={ cn(
|
|
396
|
+
'min-w-[8rem] overflow-hidden rounded-lg p-1',
|
|
397
|
+
'bg-[var(--color-popover-bg)] shadow-[var(--shadow-popover)]',
|
|
398
|
+
'border border-[var(--color-border-divider)]',
|
|
399
|
+
className,
|
|
400
|
+
) }
|
|
401
|
+
>
|
|
402
|
+
{ stampIndices(children, counter) }
|
|
403
|
+
</GuiSelect.Viewport>
|
|
404
|
+
</GuiSelect.Content>
|
|
405
|
+
);
|
|
412
406
|
},
|
|
413
407
|
);
|
|
414
408
|
|
|
@@ -420,17 +414,44 @@ export interface SelectItemProps {
|
|
|
420
414
|
item: SelectOption;
|
|
421
415
|
children?: React.ReactNode;
|
|
422
416
|
className?: string;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The row's position in the list. gui's Select is positional — it keeps its
|
|
420
|
+
* own map of index to value to drive type-ahead, the active descendant and
|
|
421
|
+
* scrolling, so a row without one is a row the keyboard cannot reach. It is
|
|
422
|
+
* stamped by `SelectContent` rather than asked of the caller, because the
|
|
423
|
+
* caller writing `<SelectItem item={x}/>` has no idea what its own ordinal is
|
|
424
|
+
* and would have to be told twice.
|
|
425
|
+
*/
|
|
426
|
+
index?: number;
|
|
423
427
|
}
|
|
424
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Number every `SelectItem` beneath a node, depth first, in render order.
|
|
431
|
+
*
|
|
432
|
+
* Depth first because rows are routinely nested — inside a `SelectItemGroup`,
|
|
433
|
+
* inside the `React.Fragment` that pairs a row with its `afterElement`. A
|
|
434
|
+
* shallow pass over the content's direct children would number the groups and
|
|
435
|
+
* leave every actual row at index 0, which reads as a list whose keyboard
|
|
436
|
+
* silently only ever lands on the first entry.
|
|
437
|
+
*/
|
|
438
|
+
const stampIndices = (children: React.ReactNode, next: () => number): React.ReactNode =>
|
|
439
|
+
React.Children.map(children, (child) => {
|
|
440
|
+
if (!React.isValidElement(child)) return child;
|
|
441
|
+
if (child.type === SelectItem) return React.cloneElement(child, { index: next() } as never);
|
|
442
|
+
const kids = (child.props as { children?: React.ReactNode }).children;
|
|
443
|
+
return kids === undefined ? child : React.cloneElement(child, undefined, stampIndices(kids, next));
|
|
444
|
+
});
|
|
445
|
+
|
|
425
446
|
export const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
|
|
426
447
|
function SelectItem(props, ref) {
|
|
427
|
-
const { item, children, className, ...rest } = props;
|
|
448
|
+
const { item, children, className, index, ...rest } = props;
|
|
428
449
|
|
|
429
450
|
return (
|
|
430
|
-
<
|
|
431
|
-
ref={ ref }
|
|
451
|
+
<GuiSelect.Item
|
|
452
|
+
ref={ ref as never }
|
|
453
|
+
index={ index ?? 0 }
|
|
432
454
|
value={ item.value }
|
|
433
|
-
textValue={ item.label }
|
|
434
455
|
className={ cn(
|
|
435
456
|
'relative flex cursor-pointer select-none items-center gap-2',
|
|
436
457
|
'rounded-md px-3 py-2 text-sm outline-none',
|
|
@@ -443,13 +464,13 @@ export const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
|
|
|
443
464
|
{ ...rest }
|
|
444
465
|
>
|
|
445
466
|
{ item.icon }
|
|
446
|
-
<
|
|
447
|
-
{ children }
|
|
448
|
-
</
|
|
449
|
-
<
|
|
467
|
+
<GuiSelect.ItemText>
|
|
468
|
+
{ children ?? item.label }
|
|
469
|
+
</GuiSelect.ItemText>
|
|
470
|
+
<GuiSelect.ItemIndicator className="ml-auto shrink-0">
|
|
450
471
|
<CheckIcon className="h-5 w-5"/>
|
|
451
|
-
</
|
|
452
|
-
</
|
|
472
|
+
</GuiSelect.ItemIndicator>
|
|
473
|
+
</GuiSelect.Item>
|
|
453
474
|
);
|
|
454
475
|
},
|
|
455
476
|
);
|
|
@@ -529,14 +550,14 @@ export const SelectValueText = React.forwardRef<HTMLSpanElement, SelectValueText
|
|
|
529
550
|
// Radix SelectValue renders the selected value or placeholder.
|
|
530
551
|
// We override with our custom content.
|
|
531
552
|
return (
|
|
532
|
-
<
|
|
533
|
-
ref={ ref }
|
|
553
|
+
<GuiSelect.Value
|
|
554
|
+
ref={ ref as never }
|
|
534
555
|
placeholder={ placeholderText }
|
|
535
556
|
className={ cn('flex flex-col justify-center min-w-0 truncate', className) }
|
|
536
557
|
{ ...rest }
|
|
537
558
|
>
|
|
538
559
|
{ content }
|
|
539
|
-
</
|
|
560
|
+
</GuiSelect.Value>
|
|
540
561
|
);
|
|
541
562
|
},
|
|
542
563
|
);
|
|
@@ -555,12 +576,12 @@ export const SelectItemGroup = React.forwardRef<HTMLDivElement, SelectItemGroupP
|
|
|
555
576
|
function SelectItemGroup(props, ref) {
|
|
556
577
|
const { children, label, className, ...rest } = props;
|
|
557
578
|
return (
|
|
558
|
-
<
|
|
559
|
-
<
|
|
579
|
+
<GuiSelect.Group ref={ ref as never } className={ cn('py-1', className) } { ...rest }>
|
|
580
|
+
<GuiSelect.Label className="px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)]">
|
|
560
581
|
{ label }
|
|
561
|
-
</
|
|
582
|
+
</GuiSelect.Label>
|
|
562
583
|
{ children }
|
|
563
|
-
</
|
|
584
|
+
</GuiSelect.Group>
|
|
564
585
|
);
|
|
565
586
|
},
|
|
566
587
|
);
|
|
@@ -569,8 +590,8 @@ export const SelectItemGroup = React.forwardRef<HTMLDivElement, SelectItemGroupP
|
|
|
569
590
|
// SelectLabel / SelectItemText
|
|
570
591
|
// ---------------------------------------------------------------------------
|
|
571
592
|
|
|
572
|
-
export const SelectLabel =
|
|
573
|
-
export const SelectItemText =
|
|
593
|
+
export const SelectLabel = GuiSelect.Label;
|
|
594
|
+
export const SelectItemText = GuiSelect.ItemText;
|
|
574
595
|
|
|
575
596
|
// ---------------------------------------------------------------------------
|
|
576
597
|
// Select (composite)
|
package/src/separator.tsx
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Separator as GuiSeparator } from '@hanzo/gui';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
import { cn } from './utils';
|
|
5
|
-
|
|
6
4
|
type Orientation = 'horizontal' | 'vertical';
|
|
7
5
|
type Variant = 'solid' | 'dashed' | 'dotted';
|
|
8
6
|
type Size = 'xs' | 'sm' | 'md' | 'lg';
|
|
@@ -13,50 +11,31 @@ export interface SeparatorProps extends React.HTMLAttributes<HTMLHRElement> {
|
|
|
13
11
|
readonly size?: Size;
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
dashed: 'border-dashed',
|
|
19
|
-
dotted: 'border-dotted',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const HORIZONTAL_SIZE_CLASSES: Record<Size, string> = {
|
|
23
|
-
xs: 'border-t-[0.5px]',
|
|
24
|
-
sm: 'border-t',
|
|
25
|
-
md: 'border-t-2',
|
|
26
|
-
lg: 'border-t-[3px]',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const VERTICAL_SIZE_CLASSES: Record<Size, string> = {
|
|
30
|
-
xs: 'border-l-[0.5px]',
|
|
31
|
-
sm: 'border-l',
|
|
32
|
-
md: 'border-l-2',
|
|
33
|
-
lg: 'border-l-[3px]',
|
|
34
|
-
};
|
|
14
|
+
/** Border weight by size — the rule is one edge, so the weight is one number. */
|
|
15
|
+
const WEIGHT: Record<Size, number> = { xs: 0.5, sm: 1, md: 2, lg: 3 };
|
|
35
16
|
|
|
36
17
|
// gui's Separator wearing the Lux border scale. `vertical` is the primitive's
|
|
37
|
-
// own orientation variant, so it lays out correctly on native too;
|
|
38
|
-
//
|
|
18
|
+
// own orientation variant, so it lays out correctly on native too; weight and
|
|
19
|
+
// dash pattern are props rather than classes for the same reason.
|
|
39
20
|
export const Separator = React.forwardRef<HTMLHRElement, SeparatorProps>(
|
|
40
21
|
function Separator(
|
|
41
|
-
{ orientation = 'horizontal', variant = 'solid', size = 'sm',
|
|
22
|
+
{ orientation = 'horizontal', variant = 'solid', size = 'sm', ...rest },
|
|
42
23
|
ref,
|
|
43
24
|
) {
|
|
44
|
-
const
|
|
25
|
+
const vertical = orientation === 'vertical';
|
|
45
26
|
|
|
46
27
|
return (
|
|
47
28
|
<GuiSeparator
|
|
48
29
|
ref={ ref as never }
|
|
49
|
-
vertical={
|
|
30
|
+
vertical={ vertical }
|
|
50
31
|
role="separator"
|
|
51
32
|
aria-orientation={ orientation }
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
className,
|
|
59
|
-
) }
|
|
33
|
+
borderWidth={ 0 }
|
|
34
|
+
borderStyle={ variant }
|
|
35
|
+
borderColor={ 'var(--color-border-divider)' as never }
|
|
36
|
+
{ ...(vertical ?
|
|
37
|
+
{ borderLeftWidth: WEIGHT[size], alignSelf: 'stretch', height: 'auto', width: 0 } :
|
|
38
|
+
{ borderTopWidth: WEIGHT[size], width: '100%' }) }
|
|
60
39
|
{ ...(rest as object) }
|
|
61
40
|
/>
|
|
62
41
|
);
|
package/src/slider.tsx
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Slider as GuiSlider } from '@hanzo/gui';
|
|
1
|
+
import { Slider as GuiSlider, Text, View, XStack, YStack } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
-
import { cn } from './utils';
|
|
5
|
-
|
|
6
4
|
export interface SliderProps {
|
|
7
5
|
readonly marks?: Array<number | { readonly value: number; readonly label: React.ReactNode }>;
|
|
8
6
|
readonly label?: React.ReactNode;
|
|
@@ -20,6 +18,12 @@ export interface SliderProps {
|
|
|
20
18
|
readonly className?: string;
|
|
21
19
|
}
|
|
22
20
|
|
|
21
|
+
const MUTED = 'var(--color-text-secondary)' as never;
|
|
22
|
+
const TRACK = 'var(--color-border-divider)' as never;
|
|
23
|
+
const ACTIVE = 'var(--color-link-primary)' as never;
|
|
24
|
+
const ACTIVE_HOVER = 'var(--color-link-primary-hover)' as never;
|
|
25
|
+
const WHITE = 'var(--color-white)' as never;
|
|
26
|
+
|
|
23
27
|
// gui's Slider: pointer, touch and keyboard from one implementation, with the
|
|
24
28
|
// drag handled as a pan gesture by the engine rather than by mouse events — so it
|
|
25
29
|
// works under a finger, not only under a cursor. `role=slider` and the arrow-key
|
|
@@ -64,30 +68,32 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
64
68
|
}, [ onValueCommit ]);
|
|
65
69
|
|
|
66
70
|
return (
|
|
67
|
-
<
|
|
71
|
+
<YStack className={ className } gap={ 4 }>
|
|
68
72
|
{ label && !showValue && (
|
|
69
|
-
<
|
|
73
|
+
<Text fontSize={ 14 } fontWeight="500" color={ MUTED }>
|
|
70
74
|
{ label }
|
|
71
|
-
</
|
|
75
|
+
</Text>
|
|
72
76
|
) }
|
|
73
77
|
{ label && showValue && (
|
|
74
|
-
<
|
|
75
|
-
<
|
|
78
|
+
<XStack alignItems="center" justifyContent="space-between">
|
|
79
|
+
<Text fontSize={ 14 } fontWeight="500" color={ MUTED }>
|
|
76
80
|
{ label }
|
|
77
|
-
</
|
|
78
|
-
<
|
|
81
|
+
</Text>
|
|
82
|
+
<Text fontSize={ 14 } color={ MUTED }>
|
|
79
83
|
{ (value ?? defaultValue ?? [])?.join(', ') }
|
|
80
|
-
</
|
|
81
|
-
</
|
|
84
|
+
</Text>
|
|
85
|
+
</XStack>
|
|
82
86
|
) }
|
|
83
87
|
|
|
84
88
|
<GuiSlider
|
|
85
89
|
ref={ ref as never }
|
|
86
90
|
unstyled
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
position="relative"
|
|
92
|
+
flexDirection="row"
|
|
93
|
+
width="100%"
|
|
94
|
+
userSelect="none"
|
|
95
|
+
alignItems="center"
|
|
96
|
+
marginBottom={ hasMarkLabel ? 24 : 0 }
|
|
91
97
|
value={ value }
|
|
92
98
|
defaultValue={ defaultValue }
|
|
93
99
|
min={ min }
|
|
@@ -99,14 +105,8 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
99
105
|
onValueChange={ handleValueChange }
|
|
100
106
|
onSlideEnd={ handleSlideEnd }
|
|
101
107
|
>
|
|
102
|
-
<GuiSlider.Track
|
|
103
|
-
unstyled
|
|
104
|
-
className="relative grow h-1.5 rounded-full bg-border-divider"
|
|
105
|
-
>
|
|
106
|
-
<GuiSlider.TrackActive
|
|
107
|
-
unstyled
|
|
108
|
-
className="absolute h-full rounded-full bg-link-primary"
|
|
109
|
-
/>
|
|
108
|
+
<GuiSlider.Track unstyled position="relative" flexGrow={ 1 } height={ 6 } borderRadius={ 9999 } backgroundColor={ TRACK }>
|
|
109
|
+
<GuiSlider.TrackActive unstyled position="absolute" height="100%" borderRadius={ 9999 } backgroundColor={ ACTIVE }/>
|
|
110
110
|
</GuiSlider.Track>
|
|
111
111
|
|
|
112
112
|
{ resolvedValue?.map((_, index) => (
|
|
@@ -114,20 +114,23 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
114
114
|
key={ index }
|
|
115
115
|
index={ index }
|
|
116
116
|
unstyled
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
width={ 20 }
|
|
118
|
+
height={ 20 }
|
|
119
|
+
borderRadius={ 9999 }
|
|
120
|
+
borderWidth={ 2 }
|
|
121
|
+
borderColor={ ACTIVE }
|
|
122
|
+
backgroundColor={ WHITE }
|
|
123
|
+
boxShadow="0 1px 2px 0 rgba(0,0,0,0.05)"
|
|
124
|
+
transition="quick"
|
|
125
|
+
hoverStyle={{ borderColor: ACTIVE_HOVER }}
|
|
126
|
+
focusStyle={{ outlineWidth: 2, outlineStyle: 'solid', outlineColor: ACTIVE, outlineOffset: 2 }}
|
|
127
|
+
disabledStyle={{ pointerEvents: 'none', opacity: 0.5 }}
|
|
125
128
|
/>
|
|
126
129
|
)) }
|
|
127
130
|
</GuiSlider>
|
|
128
131
|
|
|
129
132
|
<SliderMarks marks={ marks } min={ min } max={ max }/>
|
|
130
|
-
</
|
|
133
|
+
</YStack>
|
|
131
134
|
);
|
|
132
135
|
},
|
|
133
136
|
);
|
|
@@ -145,24 +148,20 @@ function SliderMarks(props: SliderMarksProps): React.ReactNode {
|
|
|
145
148
|
const range = max - min;
|
|
146
149
|
|
|
147
150
|
return (
|
|
148
|
-
<
|
|
151
|
+
<View position="relative" width="100%" height={ 16 }>
|
|
149
152
|
{ marks.map((mark, index) => {
|
|
150
153
|
const percent = ((mark.value - min) / range) * 100;
|
|
151
154
|
return (
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
className="absolute flex flex-col items-center -translate-x-1/2"
|
|
155
|
-
style={{ left: `${ percent }%` }}
|
|
156
|
-
>
|
|
157
|
-
<div className="h-1.5 w-0.5 bg-border-divider"/>
|
|
155
|
+
<View key={ index } position="absolute" left={ `${ percent }%` as never } alignItems="center">
|
|
156
|
+
<View width={ 2 } height={ 6 } backgroundColor={ TRACK }/>
|
|
158
157
|
{ mark.label != null && (
|
|
159
|
-
<
|
|
158
|
+
<Text marginTop={ 2 } fontSize={ 12 } color={ MUTED }>
|
|
160
159
|
{ mark.label }
|
|
161
|
-
</
|
|
160
|
+
</Text>
|
|
162
161
|
) }
|
|
163
|
-
</
|
|
162
|
+
</View>
|
|
164
163
|
);
|
|
165
164
|
}) }
|
|
166
|
-
</
|
|
165
|
+
</View>
|
|
167
166
|
);
|
|
168
167
|
}
|