Skip to content

Commit 2b47c08

Browse files
committed
add styles to component
1 parent 13fbe9e commit 2b47c08

File tree

7 files changed

+232
-34
lines changed

7 files changed

+232
-34
lines changed

components/dash-core-components/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
"react-select-fast-filter-options": "^0.2.3",
6161
"react-virtualized-select": "^3.1.3",
6262
"remark-math": "^3.0.1",
63-
"styled-components": "^6.1.19",
6463
"uniqid": "^5.4.0"
6564
},
6665
"devDependencies": {
@@ -77,7 +76,6 @@
7776
"@types/ramda": "^0.31.0",
7877
"@types/react": "^19.1.9",
7978
"@types/react-dom": "^19.1.7",
80-
"@types/styled-components": "^5.1.34",
8179
"@typescript-eslint/eslint-plugin": "^8.38.0",
8280
"@typescript-eslint/parser": "^8.38.0",
8381
"babel-loader": "^9.2.1",

components/dash-core-components/src/components/Input.tsx

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import {pick} from 'ramda';
2-
import React, {KeyboardEvent, KeyboardEventHandler, useCallback, useEffect, useRef, useState} from 'react';
2+
import React, {
3+
KeyboardEvent,
4+
KeyboardEventHandler,
5+
useCallback,
6+
useEffect,
7+
useRef,
8+
useState,
9+
useId,
10+
} from 'react';
311
import fastIsNumeric from 'fast-isnumeric';
412
import LoadingElement from '../utils/_LoadingElement';
5-
import { styled } from 'styled-components';
6-
13+
import './css/input.css';
714

815
const isNumeric = (val: unknown): val is number => fastIsNumeric(val);
916
const convert = (val: unknown) => (isNumeric(val) ? +val : NaN);
@@ -258,8 +265,6 @@ const inputProps: (keyof InputProps)[] = [
258265
'maxLength',
259266
'pattern',
260267
'size',
261-
'style',
262-
'id',
263268
];
264269

265270
const defaultProps: Partial<InputProps> = {
@@ -274,8 +279,6 @@ const defaultProps: Partial<InputProps> = {
274279
persistence_type: 'local',
275280
};
276281

277-
const StyledInput = styled.input``;
278-
279282
/**
280283
* A basic HTML input control for entering text, numbers, or passwords.
281284
*
@@ -288,8 +291,9 @@ export default function Input(props: InputProps) {
288291
const input = useRef(document.createElement('input'));
289292
const [value, setValue] = useState<InputProps['value']>(props.value);
290293
const [pendingEvent, setPendingEvent] = useState<number>();
294+
const inputId = useId();
291295

292-
const valprops = props.type === 'number' ? {} : {value};
296+
const valprops = props.type === 'number' ? {} : {value: value ?? ''};
293297
let {className} = props;
294298
className = 'dash-input' + (className ? ` ${className}` : '');
295299

@@ -307,13 +311,17 @@ export default function Input(props: InputProps) {
307311
);
308312

309313
const onEvent = useCallback(() => {
310-
const {value} = input.current;
314+
const {value: inputValue} = input.current;
311315
const {setProps} = props;
312-
const valueAsNumber = convert(value);
316+
const valueAsNumber = convert(inputValue);
313317
if (props.type === 'number') {
314318
setPropValue(props.value, valueAsNumber ?? value);
315319
} else {
316-
setProps({value});
320+
const propValue =
321+
inputValue === '' && props.value === undefined
322+
? undefined
323+
: inputValue;
324+
setProps({value: propValue});
317325
}
318326
setPendingEvent(undefined);
319327
}, [props.setProps]);
@@ -374,9 +382,40 @@ export default function Input(props: InputProps) {
374382
[pendingEvent]
375383
);
376384

385+
const handleStepperClick = useCallback(
386+
(direction: 'increment' | 'decrement') => {
387+
const currentValue = parseFloat(input.current.value) || 0;
388+
const step = parseFloat(props.step as string) || 1;
389+
const newValue =
390+
direction === 'increment'
391+
? currentValue + step
392+
: currentValue - step;
393+
394+
// Apply min/max constraints
395+
let constrainedValue = newValue;
396+
if (props.min !== undefined) {
397+
constrainedValue = Math.max(
398+
constrainedValue,
399+
parseFloat(props.min as string)
400+
);
401+
}
402+
if (props.max !== undefined) {
403+
constrainedValue = Math.min(
404+
constrainedValue,
405+
parseFloat(props.max as string)
406+
);
407+
}
408+
409+
input.current.value = constrainedValue.toString();
410+
setValue(constrainedValue.toString());
411+
onEvent();
412+
},
413+
[props.step, props.min, props.max, onEvent]
414+
);
415+
377416
useEffect(() => {
378417
const {value} = input.current;
379-
if (pendingEvent) {
418+
if (pendingEvent || props.value === value) {
380419
return;
381420
}
382421
const valueAsNumber = convert(value);
@@ -387,6 +426,11 @@ export default function Input(props: InputProps) {
387426
}, [props.value, props.type, pendingEvent]);
388427

389428
useEffect(() => {
429+
// Skip this effect if the value change came from props update (not user input)
430+
if (value === props.value) {
431+
return;
432+
}
433+
390434
const {debounce, type} = props;
391435
const {selectionStart: cursorPosition} = input.current;
392436
if (debounce) {
@@ -404,23 +448,67 @@ export default function Input(props: InputProps) {
404448
} else {
405449
onEvent();
406450
}
407-
}, [value, props.debounce]);
451+
}, [value, props.debounce, props.value]);
408452

409453
const pickedInputs = pick(inputProps, props);
410454

455+
const isNumberInput = props.type === 'number';
456+
const currentNumericValue = convert(input.current.value || '0');
457+
const minValue = convert(props.min);
458+
const maxValue = convert(props.max);
459+
const disabled = [true, 'disabled', 'DISABLED'].includes(
460+
props.disabled ?? false
461+
);
462+
const isDecrementDisabled = disabled || currentNumericValue <= minValue;
463+
const isIncrementDisabled = disabled || currentNumericValue >= maxValue;
464+
411465
return (
412466
<LoadingElement>
413-
{(loadingProps) => (
414-
<StyledInput
415-
className={className}
416-
ref={input}
417-
onBlur={onBlur}
418-
onChange={onChange}
419-
onKeyPress={onKeyPress}
420-
{...valprops}
421-
{...pickedInputs}
422-
{...loadingProps}
423-
/>
467+
{loadingProps => (
468+
<div
469+
id={props.id}
470+
className={`dash-input-container ${className}${
471+
props.type === 'hidden' ? ' dash-input-hidden' : ''
472+
}`.trim()}
473+
style={props.style}
474+
>
475+
<input
476+
id={inputId}
477+
ref={input}
478+
className="dash-input-element"
479+
onBlur={onBlur}
480+
onChange={onChange}
481+
onKeyPress={onKeyPress}
482+
{...valprops}
483+
{...pickedInputs}
484+
{...loadingProps}
485+
disabled={disabled}
486+
/>
487+
{isNumberInput && (
488+
<button
489+
type="button"
490+
className="dash-input-stepper dash-stepper-decrement"
491+
onClick={() => handleStepperClick('decrement')}
492+
disabled={isDecrementDisabled}
493+
aria-controls={inputId}
494+
aria-label="Decrease value"
495+
>
496+
497+
</button>
498+
)}
499+
{isNumberInput && (
500+
<button
501+
type="button"
502+
className="dash-input-stepper dash-stepper-increment"
503+
onClick={() => handleStepperClick('increment')}
504+
disabled={isIncrementDisabled}
505+
aria-controls={inputId}
506+
aria-label="Increase value"
507+
>
508+
+
509+
</button>
510+
)}
511+
</div>
424512
)}
425513
</LoadingElement>
426514
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
:root {
2+
--Dash-Spacing: 4px;
3+
--Dash-Stroke-Strong: rgba(0, 18, 77, 0.45);
4+
--Dash-Stroke-Weak: rgba(0, 24, 102, 0.1);
5+
--Dash-Fill-Weak: rgba(0, 30, 128, 0.04);
6+
--Dash-Fill-Inverse-strong: #fff;
7+
--Dash-Text-Primary: rgba(0, 18, 77, 0.87);
8+
--Dash-Fill-Primary-Hover: rgba(0, 18, 77, 0.04);
9+
--Dash-Fill-Primary-Active: rgba(0, 18, 77, 0.08);
10+
}
Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,113 @@
1-
input.dash-input:invalid {
1+
/* Input container styles */
2+
.dash-input-container {
3+
position: relative;
4+
display: inline-flex;
5+
align-items: center;
6+
width: 170px; /* default input width */
7+
height: 32px;
8+
border-radius: var(--Dash-Spacing);
9+
border: 1px solid var(--Dash-Stroke-Strong);
10+
box-sizing: border-box;
11+
container-type: inline-size;
12+
vertical-align: middle;
13+
}
14+
15+
/* Input field styles */
16+
.dash-input-element {
17+
padding: var(--Dash-Spacing) calc(2 * var(--Dash-Spacing));
18+
background: var(--Dash-Fill-Inverse-strong);
19+
border: none;
20+
border-radius: var(--Dash-Spacing);
21+
flex: 1 1 0;
22+
min-width: 0;
23+
height: 100%;
24+
box-sizing: border-box;
25+
z-index: 1;
26+
order: 2;
27+
}
28+
29+
/* Hide native steppers for number inputs */
30+
.dash-input-element[type='number'] {
31+
-moz-appearance: textfield;
32+
}
33+
34+
.dash-input-element[type='number']::-webkit-outer-spin-button,
35+
.dash-input-element[type='number']::-webkit-inner-spin-button {
36+
-webkit-appearance: none;
37+
margin: 0;
38+
}
39+
40+
/* Show native steppers when container is narrow and input is number type */
41+
@container (max-width: 99px) {
42+
.dash-input-element[type='number'] {
43+
-moz-appearance: number-input;
44+
}
45+
46+
.dash-input-element[type='number']::-webkit-outer-spin-button,
47+
.dash-input-element[type='number']::-webkit-inner-spin-button {
48+
-webkit-appearance: inner-spin-button;
49+
margin: 0;
50+
}
51+
}
52+
53+
/* Stepper button base styles */
54+
.dash-input-stepper {
55+
display: flex;
56+
align-items: center;
57+
justify-content: center;
58+
width: 32px;
59+
height: 100%;
60+
border: none;
61+
background: var(--Dash-Fill-Inverse-strong);
62+
cursor: pointer;
63+
font-size: 16px;
64+
font-weight: bold;
65+
color: var(--Dash-Text-Primary);
66+
}
67+
68+
.dash-input-stepper:hover {
69+
background: var(--Dash-Fill-Primary-Hover);
70+
}
71+
72+
.dash-input-stepper:active {
73+
background: var(--Dash-Fill-Primary-Active);
74+
}
75+
76+
.dash-input-stepper:disabled {
77+
background: var(--Dash-Fill-Inverse-strong);
78+
opacity: 0.5;
79+
cursor: default;
80+
}
81+
82+
@container (max-width: 99px) {
83+
.dash-input-stepper {
84+
display: none;
85+
}
86+
}
87+
88+
/* Decrement button specific styles */
89+
.dash-input-stepper.dash-stepper-decrement {
90+
border-right: 1px solid var(--Dash-Stroke-Weak);
91+
border-radius: var(--Dash-Spacing) 0 0 var(--Dash-Spacing);
92+
order: 1;
93+
}
94+
95+
/* Increment button specific styles */
96+
.dash-input-stepper.dash-stepper-increment {
97+
border-left: 1px solid var(--Dash-Stroke-Weak);
98+
border-radius: 0 var(--Dash-Spacing) var(--Dash-Spacing) 0;
99+
order: 3;
100+
}
101+
102+
/* Hidden input type */
103+
.dash-input-container.dash-input-hidden {
104+
display: none;
105+
}
106+
107+
input.dash-input-element:invalid {
2108
outline: solid red;
3109
}
4110

5-
input.dash-input:valid {
111+
input.dash-input-element:valid {
6112
outline: none black;
7113
}

0 commit comments

Comments
 (0)