Skip to content

Commit 55e73a9

Browse files
authored
feat: Implement new Input control component
1 parent c567e86 commit 55e73a9

File tree

28 files changed

+662
-27
lines changed

28 files changed

+662
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Component, For } from 'solid-js';
2+
import { Container, Heading, Divider, Input } from '@spuxx/solid';
3+
4+
export const InputRoute: Component = () => {
5+
function validate(_value: string, event: Event) {
6+
const input = event.currentTarget as HTMLInputElement;
7+
input.reportValidity();
8+
}
9+
10+
const variants = ['contained', 'outlined'];
11+
12+
return (
13+
<>
14+
<Container tag="article">
15+
<Heading level={1}>Input</Heading>
16+
<Divider color="gradient" />
17+
<Container variant="contained" color="background">
18+
<Heading level={2}>Variants</Heading>
19+
<Divider color="gradient" />
20+
<Input label="contained (default)" class="m-1" />
21+
<Input label="outlined" variant="outlined" class="m-1" />
22+
<Input label="contained, disabled" class="m-1" disabled />
23+
<Input label="outlined, disabled" variant="outlined" class="m-1" disabled />
24+
</Container>
25+
<Container variant="contained" color="background">
26+
<Heading level={2}>Validation</Heading>
27+
<Divider color="gradient" />
28+
<For each={variants}>
29+
{(variant) => (
30+
<>
31+
<Input
32+
label="required"
33+
class="m-1"
34+
required
35+
attrs={{ minlength: 5 }}
36+
onInput={validate}
37+
variant={variant}
38+
/>
39+
<Input
40+
label="Fruit"
41+
class="m-1"
42+
icon="mdi:fruit-cherries"
43+
variant={variant}
44+
options={[
45+
{
46+
value: 'Apple',
47+
label: 'A healthy apple.',
48+
},
49+
{
50+
value: 'Banana',
51+
label: 'A sweet banana.',
52+
},
53+
{
54+
value: 'Melon',
55+
label: 'A juicy melon.',
56+
},
57+
]}
58+
forceOption
59+
attrs={{ type: 'text' }}
60+
onInput={validate}
61+
/>
62+
</>
63+
)}
64+
</For>
65+
</Container>
66+
<Container variant="contained" color="background">
67+
<Heading level={2}>Sizes</Heading>
68+
<Divider color="gradient" />
69+
<For each={variants}>
70+
{(variant) => (
71+
<>
72+
<Input label="auto (default)" class="m-1" variant={variant} />
73+
<Input label="small" class="m-1" size="small" variant={variant} />
74+
<Input label="medium" class="m-1" size="medium" variant={variant} />
75+
<Input label="large" class="m-1" size="large" variant={variant} />
76+
<Input label="full" class="m-1" size="full" variant={variant} />
77+
</>
78+
)}
79+
</For>
80+
</Container>
81+
<Container variant="contained" color="background">
82+
<Heading level={2}>With Icon</Heading>
83+
<Divider color="gradient" />
84+
<For each={variants}>
85+
{(variant) => (
86+
<>
87+
<Input
88+
label="Username"
89+
class="m-1"
90+
icon="mdi:account"
91+
attrs={{ type: 'text' }}
92+
variant={variant}
93+
/>
94+
<Input
95+
label="Password"
96+
class="m-1"
97+
icon="mdi:lock"
98+
attrs={{ type: 'password' }}
99+
variant={variant}
100+
/>
101+
</>
102+
)}
103+
</For>
104+
</Container>
105+
<Container variant="contained" color="background">
106+
<Heading level={2}>Input Types</Heading>
107+
<Divider color="gradient" />
108+
<For each={variants}>
109+
{(variant) => (
110+
<>
111+
<Input
112+
label="Username"
113+
class="m-1"
114+
icon="mdi:account"
115+
attrs={{ type: 'text' }}
116+
variant={variant}
117+
/>
118+
<Input
119+
label="Password"
120+
class="m-1"
121+
icon="mdi:lock"
122+
attrs={{ type: 'password' }}
123+
variant={variant}
124+
/>
125+
</>
126+
)}
127+
</For>
128+
</Container>
129+
</Container>
130+
</>
131+
);
132+
};

apps/solid/src/routes/routes.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import { RouteProps } from '@solidjs/router';
22
import { IndexRoute } from './index.route';
3-
import { ButtonRoute } from './components/input/button.route';
3+
import { ButtonRoute } from './components/control/button.route';
44
import { ContainerRoute } from './components/layout/container.route';
55
import { DividerRoute } from './components/layout/divider.route';
66
import { DialogRoute } from './dialog.route';
7+
import { InputRoute } from './components/control/input.route';
78

89
export const routes: RouteProps[] = [
910
{
1011
path: '/',
1112
component: IndexRoute,
1213
},
1314
{
14-
path: '/components/input/button',
15+
path: '/components/control/button',
1516
component: () => ButtonRoute,
1617
},
18+
{
19+
path: '/components/control/input',
20+
component: () => InputRoute,
21+
},
1722
{
1823
path: '/components/layout/container',
1924
component: () => ContainerRoute,

apps/starlight/src/assets/sidebar.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ export const sidebar: StarlightUserConfigWithPlugins['sidebar'] = [
5151
label: 'Components',
5252
items: [
5353
{
54-
label: 'Input',
55-
autogenerate: { directory: 'solid/components/input' },
54+
label: 'Control',
55+
autogenerate: { directory: 'solid/components/control' },
5656
},
5757
{
5858
label: 'Layout',

packages/browser-utils/src/styles/colors.css

+14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
color: var(--spx-color-surface);
99
}
1010

11+
.spx[spx-variant='colored'].spx[spx-color='accent'] {
12+
color: var(--spx-color-accent);
13+
}
14+
1115
.spx[spx-variant='colored'].spx[spx-color='primary'] {
1216
color: var(--spx-color-primary);
1317
}
@@ -50,6 +54,11 @@
5054
color: var(--spx-color-on-surface);
5155
}
5256

57+
.spx[spx-variant='contained'].spx[spx-color='accent'] {
58+
background: var(--spx-color-accent);
59+
color: var(--spx-color-on-accent);
60+
}
61+
5362
.spx[spx-variant='contained'].spx[spx-color='primary'] {
5463
background: var(--spx-color-primary);
5564
color: var(--spx-color-on-primary);
@@ -96,6 +105,11 @@
96105
color: var(--spx-color-surface);
97106
}
98107

108+
.spx[spx-variant='outlined'].spx[spx-color='accent'] {
109+
border-color: var(--spx-color-accent);
110+
color: var(--spx-color-accent);
111+
}
112+
99113
.spx[spx-variant='outlined'].spx[spx-color='primary'] {
100114
border-color: var(--spx-color-primary);
101115
color: var(--spx-color-primary);
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
@import './components/input/button.css';
1+
@import './components/control/button.css';
2+
@import './components/control/input.css';
23
@import './components/layout/divider.css';
34
@import './components/layout/container.css';
45
@import './components/typography/heading.css';

packages/browser-utils/src/styles/components/input/button.css packages/browser-utils/src/styles/components/control/button.css

+15
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,18 @@
4747
.spx-button[spx-rounded] {
4848
border-radius: calc(var(--spx-control-height-default) / 2) !important;
4949
}
50+
51+
.spx-button[spx-size='small'] {
52+
min-width: var(--spx-control-width-small);
53+
max-width: min(var(--spx-control-width-small), 100%);
54+
}
55+
56+
.spx-button[spx-size='medium'] {
57+
min-width: var(--spx-control-width-medium);
58+
max-width: min(var(--spx-control-width-medium), 100%);
59+
}
60+
61+
.spx-button[spx-size='large'] {
62+
min-width: var(--spx-control-width-large);
63+
max-width: min(var(--spx-control-width-large), 100%);
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
.spx-input {
2+
--spx-input-pd-x: 0.75rem;
3+
4+
position: relative;
5+
height: fit-content;
6+
display: inline-flex;
7+
border-radius: var(--spx-border-radius);
8+
background: inherit;
9+
pointer-events: none;
10+
11+
input {
12+
height: var(--spx-input-height-default);
13+
line-height: var(--spx-input-height-default);
14+
width: 100%;
15+
box-sizing: border-box;
16+
font-size: medium;
17+
background: inherit;
18+
border: none;
19+
border-radius: var(--spx-border-radius);
20+
padding: 0px var(--spx-input-pd-x);
21+
pointer-events: all;
22+
}
23+
24+
input:focus-visible {
25+
outline: none;
26+
}
27+
28+
label {
29+
position: absolute;
30+
font-size: medium;
31+
top: 50%;
32+
transform: translateY(-50%);
33+
transition:
34+
padding 200ms,
35+
font-size 200ms,
36+
transform 200ms;
37+
pointer-events: none;
38+
display: flex;
39+
gap: 2px;
40+
align-items: center;
41+
}
42+
43+
input::-webkit-calendar-picker-indicator,
44+
[list]::-webkit-calendar-picker-indicator {
45+
display: none !important;
46+
}
47+
48+
&:has(input:disabled) {
49+
filter: var(--spx-filter-control-disabled);
50+
}
51+
}
52+
53+
.spx-input[spx-size='small'] {
54+
width: var(--spx-control-width-small);
55+
max-width: 100%;
56+
}
57+
58+
.spx-input[spx-size='medium'] {
59+
width: var(--spx-control-width-medium);
60+
max-width: 100%;
61+
}
62+
63+
.spx-input[spx-size='large'] {
64+
width: var(--spx-control-width-large);
65+
max-width: 100%;
66+
}
67+
68+
/* Common */
69+
.spx-input[spx-variant='contained'],
70+
.spx-input[spx-variant='outlined'] {
71+
label {
72+
left: var(--spx-input-pd-x);
73+
}
74+
}
75+
76+
/* Contained */
77+
.spx-input[spx-variant='contained'] {
78+
border-bottom-left-radius: 0px;
79+
border-bottom-right-radius: 0px;
80+
background: var(--spx-color-input-contained-bg);
81+
82+
input {
83+
color: inherit;
84+
box-sizing: border-box;
85+
border-bottom-left-radius: 0px;
86+
border-bottom-right-radius: 0px;
87+
padding-top: 1.25rem;
88+
}
89+
90+
label {
91+
color: var(--spx-color-text-subtle);
92+
}
93+
94+
input:focus {
95+
box-shadow: 0 var(--spx-border-width) 0 var(--spx-color-input-focus);
96+
}
97+
98+
input:focus + label {
99+
transform: translateY(-2.15rem);
100+
color: var(--spx-color-input-focus);
101+
}
102+
103+
input:focus + label,
104+
input:not(:placeholder-shown) + label {
105+
transform: translateY(-1.15rem);
106+
font-size: smaller;
107+
}
108+
}
109+
110+
.spx-input[spx-variant='contained']:has(input:invalid) {
111+
border-color: var(--spx-color-error);
112+
113+
label {
114+
color: var(--spx-color-input-error);
115+
}
116+
117+
input {
118+
box-shadow: 0 var(--spx-border-width) 0 var(--spx-color-input-error);
119+
}
120+
}
121+
122+
/* Outlined */
123+
.spx-input[spx-variant='outlined'] {
124+
background: inherit;
125+
color: var(--spx-color-input-outlined-fg);
126+
border-color: var(--spx-color-input-outlined-fg);
127+
128+
input {
129+
color: var(--spx-color-text-default);
130+
}
131+
132+
label {
133+
padding: 0px 4px;
134+
left: calc(var(--spx-input-pd-x) - 4px);
135+
background: inherit;
136+
}
137+
138+
input:focus + label {
139+
transform: translateY(-2.15rem);
140+
color: var(--spx-color-input-focus);
141+
}
142+
143+
input:focus + label,
144+
input:not(:placeholder-shown) + label {
145+
transform: translateY(-2.15rem);
146+
font-size: smaller;
147+
}
148+
}
149+
150+
.spx-input[spx-variant='outlined']:has(input:focus) {
151+
border-color: var(--spx-color-input-focus);
152+
}
153+
154+
.spx-input[spx-variant='outlined']:has(input:invalid) {
155+
border-color: var(--spx-color-input-error);
156+
157+
label {
158+
color: var(--spx-color-input-error);
159+
}
160+
}

0 commit comments

Comments
 (0)