@varialkit/textfield 0.1.0
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/docs.md +174 -0
- package/examples/index.tsx +1 -0
- package/package.json +26 -0
- package/src/TextField.scss +187 -0
- package/src/TextField.tsx +110 -0
- package/src/TextField.types.ts +48 -0
- package/src/index.ts +1 -0
package/docs.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# TextField
|
|
2
|
+
|
|
3
|
+
The TextField component is a single-line text input field. It is a **controlled component**, which means you must manage its state by providing a `value` and an `onChange` handler. It is used for collecting user input that is a single line, such as names, emails, or titles.
|
|
4
|
+
|
|
5
|
+
## How to Use
|
|
6
|
+
|
|
7
|
+
To use the TextField, import it from the `@solara/textfield` package and manage its state in your component.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import React, { useState } from 'react';
|
|
11
|
+
import { TextField } from "@solara/textfield";
|
|
12
|
+
|
|
13
|
+
export function MyComponent() {
|
|
14
|
+
const [value, setValue] = useState("");
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<TextField
|
|
18
|
+
id="my-textfield"
|
|
19
|
+
value={value}
|
|
20
|
+
onChange={(e) => setValue(e.target.value)}
|
|
21
|
+
placeholder="Enter your name..."
|
|
22
|
+
label="Enter your name:"
|
|
23
|
+
helperText="This is a helper text."
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Best Practices
|
|
30
|
+
|
|
31
|
+
- **Provide a Label**: Always use the `label` prop to provide a descriptive label for the text field. This is crucial for accessibility and usability.
|
|
32
|
+
- **Controlled Component**: Remember that `TextField` is a controlled component. You need to manage its `value` and `onChange` event to update the state.
|
|
33
|
+
- **Use Helper Text for Guidance**: The `helperText` prop is useful for providing additional instructions or context below the text field.
|
|
34
|
+
- **Use Placeholders Wisely**: Use placeholders to provide hints or examples of the expected input, but do not rely on them as a substitute for a label.
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
The TextField component accepts the following props:
|
|
39
|
+
|
|
40
|
+
| Prop | Type | Default | Description |
|
|
41
|
+
| ------------ | ---------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
42
|
+
| `variant` | `"default" | "quiet"` | `"default"` | The visual style of the text field. `default` has a visible border, while `quiet` is borderless. |
|
|
43
|
+
| `size` | `"small" | "medium" | "large"` | `"medium"` | Controls the text field's internal padding and font size. Use `small` for tight spaces and `large` for touch-friendly interfaces. |
|
|
44
|
+
| `radius` | `"small" | "medium" | "large" | "full"` | `"medium"` | Controls the textfield's border radius. |
|
|
45
|
+
| `isInvalid` | `boolean` | `false` | When `true`, applies a style to indicate a validation error. This is useful for form validation. |
|
|
46
|
+
| `isDisabled` | `boolean` | `false` | When `true`, the text field is not interactive and has a disabled style. This prevents users from entering text. |
|
|
47
|
+
| `label` | `string` | | The label for the text field. |
|
|
48
|
+
| `labelPosition` | `"top" | "left"` | `"top"` | The position of the label. |
|
|
49
|
+
| `helperText` | `string` | | The helper text for the text field. |
|
|
50
|
+
| `iconLeft` | `SolaraIconName | IconProps` | | Optional leading icon rendered inside the field. |
|
|
51
|
+
| `fullWidth` | `boolean` | `false` | When `true`, the text field will take up the full width of its container. |
|
|
52
|
+
|
|
53
|
+
...and all other standard `React.InputHTMLAttributes<HTMLInputElement>` props, such as `value`, `onChange`, `id`, `placeholder`, `size`, etc.
|
|
54
|
+
|
|
55
|
+
## Label and Helper Text
|
|
56
|
+
|
|
57
|
+
The `TextField` component now includes built-in support for a `label` and `helperText`.
|
|
58
|
+
|
|
59
|
+
- The `label` prop adds a visible label to the text field, which is essential for accessibility.
|
|
60
|
+
- The `helperText` prop provides additional guidance or context below the input.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<TextField
|
|
64
|
+
label="Your Name"
|
|
65
|
+
helperText="Please enter your full name."
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Label Position
|
|
70
|
+
|
|
71
|
+
You can control the position of the label using the `labelPosition` prop. It defaults to `top`.
|
|
72
|
+
|
|
73
|
+
### Top (Default)
|
|
74
|
+
|
|
75
|
+
The label is displayed above the text field.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<TextField label="Top-aligned Label" labelPosition="top" />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Left
|
|
82
|
+
|
|
83
|
+
The label is displayed to the left of the text field.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<TextField label="Left-aligned Label" labelPosition="left" />
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Variants
|
|
90
|
+
|
|
91
|
+
The `variant` prop allows you to control the text field's visual appearance.
|
|
92
|
+
|
|
93
|
+
### Default
|
|
94
|
+
|
|
95
|
+
The default variant has a visible border and is suitable for most use cases.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<TextField placeholder="Default text field" />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Quiet
|
|
102
|
+
|
|
103
|
+
The quiet variant has no border, making it suitable for clean, minimalist layouts where the text field should be less prominent.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<TextField variant="quiet" placeholder="Quiet text field" />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Size
|
|
110
|
+
|
|
111
|
+
The `size` prop adjusts the text field's padding and font size to fit different layout requirements.
|
|
112
|
+
|
|
113
|
+
### Small
|
|
114
|
+
|
|
115
|
+
Small size has reduced padding and a smaller font size, making it suitable for compact interfaces where space is limited.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<TextField size="small" placeholder="Small text field" />
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Medium
|
|
122
|
+
|
|
123
|
+
Medium size has standard padding and font size. This is the default setting and should be used in most cases.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<TextField size="medium" placeholder="Medium text field" />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Large
|
|
130
|
+
|
|
131
|
+
Large size has increased padding and a larger font size, making it more prominent and easier to interact with on touch devices.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<TextField size="large" placeholder="Large text field" />
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Invalid State
|
|
138
|
+
|
|
139
|
+
When `isInvalid` is `true`, the text field is styled with a red border to indicate a validation error. This is commonly used in forms to show the user which fields need to be corrected.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<TextField isInvalid placeholder="Invalid text field" />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Disabled State
|
|
146
|
+
|
|
147
|
+
When `isDisabled` is `true`, the text field is not interactive and has a visually distinct style. This is useful for preventing users from entering text when it is not appropriate to do so.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<TextField isDisabled placeholder="Disabled text field" />
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Accessibility
|
|
154
|
+
|
|
155
|
+
The `TextField` component is designed with accessibility in mind.
|
|
156
|
+
|
|
157
|
+
- **Internal Label**: The component now internally handles the association between the label and the input. By providing the `label` prop, the component ensures the correct `for` and `id` attributes are set, making it accessible to screen readers.
|
|
158
|
+
- **Keyboard Navigation**: The text field is focusable and can be navigated using the keyboard.
|
|
159
|
+
|
|
160
|
+
## Icons
|
|
161
|
+
|
|
162
|
+
You can render a leading icon inside the text field. Icons inherit the text color by default.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
<TextField placeholder="Search" iconLeft="data_spreadsheet_search_24" />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Full Width
|
|
169
|
+
|
|
170
|
+
When `fullWidth` is `true`, the text field will take up the full width of its container. This is useful for creating responsive layouts that adapt to different screen sizes.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
<TextField fullWidth />
|
|
174
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { stories } from "../examples";
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@varialkit/textfield",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./examples": "./examples/index.tsx"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@varialkit/icons": "0.1.0"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"docs.md",
|
|
17
|
+
"examples"
|
|
18
|
+
],
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": "^19.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/react": "19.0.10",
|
|
24
|
+
"react": "19.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
.solara-textfield-wrapper {
|
|
2
|
+
--textfield-padding-y: var(--space-2);
|
|
3
|
+
--textfield-padding-x: var(--space-3);
|
|
4
|
+
--textfield-font-size: var(--font-size-body-scaled);
|
|
5
|
+
--textfield-line-height: var(--line-height-body-scaled);
|
|
6
|
+
--textfield-label-font-size: var(--font-size-caption-scaled);
|
|
7
|
+
--textfield-helper-font-size: var(--font-size-footnote-scaled);
|
|
8
|
+
--textfield-label-gap: var(--space-1);
|
|
9
|
+
--textfield-helper-gap: var(--space-1);
|
|
10
|
+
display: flex;
|
|
11
|
+
|
|
12
|
+
&.solara-textfield-wrapper--full-width {
|
|
13
|
+
width: 100%;
|
|
14
|
+
|
|
15
|
+
.solara-textfield-container,
|
|
16
|
+
.solara-textfield-field,
|
|
17
|
+
.solara-textfield {
|
|
18
|
+
width: 100%;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&.solara-textfield--size-small {
|
|
23
|
+
--textfield-padding-y: var(--space-1);
|
|
24
|
+
--textfield-padding-x: var(--space-2);
|
|
25
|
+
--textfield-font-size: var(--font-size-caption-scaled);
|
|
26
|
+
--textfield-line-height: var(--line-height-caption-scaled);
|
|
27
|
+
--textfield-label-font-size: var(--font-size-footnote-scaled);
|
|
28
|
+
--textfield-helper-font-size: var(--font-size-footnote-scaled);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&.solara-textfield--size-large {
|
|
32
|
+
--textfield-padding-y: var(--space-3);
|
|
33
|
+
--textfield-padding-x: var(--space-4);
|
|
34
|
+
--textfield-font-size: var(--font-size-h5-scaled);
|
|
35
|
+
--textfield-line-height: var(--line-height-body-scaled);
|
|
36
|
+
--textfield-label-font-size: var(--font-size-body-scaled);
|
|
37
|
+
--textfield-helper-font-size: var(--font-size-caption-scaled);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&--label-position-top {
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
|
|
43
|
+
.solara-textfield-label {
|
|
44
|
+
margin-bottom: calc(var(--textfield-label-gap) * var(--spacing-multiplier));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&--label-position-left {
|
|
49
|
+
flex-direction: row;
|
|
50
|
+
align-items: center;
|
|
51
|
+
|
|
52
|
+
.solara-textfield-label {
|
|
53
|
+
margin-right: calc(var(--textfield-label-gap) * var(--spacing-multiplier));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.solara-textfield {
|
|
59
|
+
--textfield-border-radius: var(--radius-2);
|
|
60
|
+
// Base styles for the textfield
|
|
61
|
+
border: 1px solid var(--color-surface-400);
|
|
62
|
+
background-color: var(--color-surface-0);
|
|
63
|
+
border-radius: calc(var(--textfield-border-radius) * var(--radius-multiplier));
|
|
64
|
+
padding: calc(var(--textfield-padding-y) * var(--spacing-multiplier))
|
|
65
|
+
calc(var(--textfield-padding-x) * var(--spacing-multiplier));
|
|
66
|
+
font-size: var(--textfield-font-size);
|
|
67
|
+
font-family: var(--font-body);
|
|
68
|
+
line-height: var(--textfield-line-height);
|
|
69
|
+
color: var(--color-text-primary);
|
|
70
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
|
71
|
+
|
|
72
|
+
&::placeholder {
|
|
73
|
+
color: var(--color-text-tertiary);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&--radius-small {
|
|
77
|
+
--textfield-border-radius: var(--radius-1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&--radius-large {
|
|
81
|
+
--textfield-border-radius: var(--radius-3);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&--radius-full {
|
|
85
|
+
--textfield-border-radius: var(--radius-full);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&:hover {
|
|
89
|
+
border-color: var(--color-primary);
|
|
90
|
+
background-color: var(--color-surface-200);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&:focus {
|
|
94
|
+
outline: none;
|
|
95
|
+
border-color: var(--color-primary);
|
|
96
|
+
box-shadow: 0 0 0 2px var(--color-primary-focus);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&:active {
|
|
100
|
+
background-color: var(--color-surface-300);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Disabled state
|
|
104
|
+
&--disabled,
|
|
105
|
+
&:disabled {
|
|
106
|
+
background-color: var(--color-surface-200);
|
|
107
|
+
color: var(--color-on-surface-disabled);
|
|
108
|
+
cursor: not-allowed;
|
|
109
|
+
border-color: var(--color-surface-300);
|
|
110
|
+
|
|
111
|
+
&:hover {
|
|
112
|
+
border-color: var(--color-surface-300);
|
|
113
|
+
background-color: var(--color-surface-200);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Invalid state
|
|
118
|
+
&--invalid {
|
|
119
|
+
border-color: var(--color-destructive);
|
|
120
|
+
|
|
121
|
+
&:focus {
|
|
122
|
+
box-shadow: 0 0 0 2px var(--color-destructive-focus);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Variants
|
|
127
|
+
&--quiet {
|
|
128
|
+
background-color: transparent;
|
|
129
|
+
border-color: transparent;
|
|
130
|
+
border-bottom: 1px solid var(--color-surface-400);
|
|
131
|
+
border-radius: 0;
|
|
132
|
+
|
|
133
|
+
&:hover,
|
|
134
|
+
&:focus {
|
|
135
|
+
border-color: var(--color-primary);
|
|
136
|
+
background-color: var(--color-surface-100);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&--with-icon {
|
|
141
|
+
padding-left: calc(
|
|
142
|
+
(var(--textfield-padding-x) * var(--spacing-multiplier)) +
|
|
143
|
+
(var(--textfield-icon-size, 1.1em)) +
|
|
144
|
+
(var(--textfield-icon-gap, var(--space-2)) * var(--spacing-multiplier))
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.solara-textfield-field {
|
|
150
|
+
position: relative;
|
|
151
|
+
display: inline-flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.solara-textfield-icon {
|
|
156
|
+
position: absolute;
|
|
157
|
+
left: calc(var(--textfield-padding-x) * var(--spacing-multiplier));
|
|
158
|
+
display: inline-flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: center;
|
|
161
|
+
pointer-events: none;
|
|
162
|
+
color: currentColor;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.solara-textfield-field .solara-icon [stroke]:not([stroke="none"]) {
|
|
166
|
+
stroke: currentColor;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.solara-textfield-field .solara-icon [fill]:not([fill="none"]) {
|
|
170
|
+
fill: currentColor;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.solara-textfield-container {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.solara-textfield-label {
|
|
179
|
+
font-size: var(--textfield-label-font-size);
|
|
180
|
+
color: var(--color-text-primary);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.solara-textfield-helper-text {
|
|
184
|
+
font-size: var(--textfield-helper-font-size);
|
|
185
|
+
color: var(--color-text-secondary);
|
|
186
|
+
margin-top: calc(var(--textfield-helper-gap) * var(--spacing-multiplier));
|
|
187
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Icon } from '@solara/icons';
|
|
3
|
+
import type { IconProps } from '@solara/icons';
|
|
4
|
+
import { TextFieldProps } from './TextField.types';
|
|
5
|
+
import './TextField.scss';
|
|
6
|
+
|
|
7
|
+
type TextFieldIcon = IconProps | IconProps['name'];
|
|
8
|
+
|
|
9
|
+
const normalizeIconProps = (icon: TextFieldIcon): IconProps =>
|
|
10
|
+
typeof icon === 'string' ? { name: icon } : icon;
|
|
11
|
+
|
|
12
|
+
const resolveIconProps = (icon: TextFieldIcon): IconProps => {
|
|
13
|
+
const iconProps = normalizeIconProps(icon);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...iconProps,
|
|
17
|
+
style: {
|
|
18
|
+
...iconProps.style,
|
|
19
|
+
// Keep textfield icons aligned with the text color.
|
|
20
|
+
color: 'currentColor',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A standard text input field for single-line input.
|
|
27
|
+
* It is a controlled component, so you must provide a `value` and `onChange` handler.
|
|
28
|
+
* It supports all the standard props of an HTML input element.
|
|
29
|
+
*/
|
|
30
|
+
export const TextField: React.FC<TextFieldProps> = ({
|
|
31
|
+
variant = 'default',
|
|
32
|
+
size = 'medium',
|
|
33
|
+
radius = 'medium',
|
|
34
|
+
isInvalid = false,
|
|
35
|
+
isDisabled = false,
|
|
36
|
+
// The label for the text field.
|
|
37
|
+
label,
|
|
38
|
+
// The position of the label.
|
|
39
|
+
labelPosition = 'top',
|
|
40
|
+
// The helper text for the text field.
|
|
41
|
+
helperText,
|
|
42
|
+
iconLeft,
|
|
43
|
+
fullWidth,
|
|
44
|
+
...props
|
|
45
|
+
}) => {
|
|
46
|
+
// The base class for the component to scope all styles.
|
|
47
|
+
const baseClass = 'solara-textfield';
|
|
48
|
+
|
|
49
|
+
// The variant class, which is determined by the `variant` prop.
|
|
50
|
+
const variantClass = `solara-textfield--${variant}`;
|
|
51
|
+
|
|
52
|
+
// The size class modifies the padding and font size of the component.
|
|
53
|
+
const sizeClass = `solara-textfield--size-${size}`;
|
|
54
|
+
|
|
55
|
+
const radiusClass = `solara-textfield--radius-${radius}`;
|
|
56
|
+
|
|
57
|
+
// The invalid class is applied when the `isInvalid` prop is true, typically for validation errors.
|
|
58
|
+
const invalidClass = isInvalid ? 'solara-textfield--invalid' : '';
|
|
59
|
+
|
|
60
|
+
// The disabled class is applied when the `isDisabled` prop is true.
|
|
61
|
+
const disabledClass = isDisabled ? 'solara-textfield--disabled' : '';
|
|
62
|
+
|
|
63
|
+
// The final classes for the component are composed of the base class and any modifier classes.
|
|
64
|
+
const classes = [
|
|
65
|
+
baseClass,
|
|
66
|
+
variantClass,
|
|
67
|
+
sizeClass,
|
|
68
|
+
radiusClass,
|
|
69
|
+
invalidClass,
|
|
70
|
+
disabledClass,
|
|
71
|
+
iconLeft ? 'solara-textfield--with-icon' : '',
|
|
72
|
+
]
|
|
73
|
+
.join(' ')
|
|
74
|
+
.trim();
|
|
75
|
+
|
|
76
|
+
const textFieldInput = (
|
|
77
|
+
<input className={classes} disabled={isDisabled} {...props} />
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const textField = iconLeft ? (
|
|
81
|
+
<div className='solara-textfield-field'>
|
|
82
|
+
<span className='solara-textfield-icon'>
|
|
83
|
+
<Icon {...resolveIconProps(iconLeft)} />
|
|
84
|
+
</span>
|
|
85
|
+
{textFieldInput}
|
|
86
|
+
</div>
|
|
87
|
+
) : (
|
|
88
|
+
textFieldInput
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const fullWidthClass = fullWidth ? 'solara-textfield-wrapper--full-width' : '';
|
|
92
|
+
|
|
93
|
+
// This wrapper handles the positioning of the label and the text field (either top or left).
|
|
94
|
+
const wrapperClass = `solara-textfield-wrapper solara-textfield-wrapper--label-position-${labelPosition} ${sizeClass} ${fullWidthClass}`.trim();
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className={wrapperClass}>
|
|
98
|
+
{/* The label is rendered only if the `label` prop is provided. */}
|
|
99
|
+
{label && <label className='solara-textfield-label'>{label}</label>}
|
|
100
|
+
{/* This container groups the text field and its helper text. */}
|
|
101
|
+
<div className='solara-textfield-container'>
|
|
102
|
+
{textField}
|
|
103
|
+
{/* The helper text is rendered only if the `helperText` prop is provided. */}
|
|
104
|
+
{helperText && (
|
|
105
|
+
<p className='solara-textfield-helper-text'>{helperText}</p>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { IconProps } from '@solara/icons';
|
|
3
|
+
|
|
4
|
+
export type TextFieldSize = 'small' | 'medium' | 'large';
|
|
5
|
+
|
|
6
|
+
export interface TextFieldProps
|
|
7
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
8
|
+
/**
|
|
9
|
+
* The variant of the text field.
|
|
10
|
+
*/
|
|
11
|
+
variant?: 'default' | 'quiet';
|
|
12
|
+
/**
|
|
13
|
+
* The size of the text field.
|
|
14
|
+
*/
|
|
15
|
+
size?: TextFieldSize;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the text field is invalid.
|
|
18
|
+
*/
|
|
19
|
+
isInvalid?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the text field is disabled.
|
|
22
|
+
*/
|
|
23
|
+
isDisabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The label for the text field.
|
|
26
|
+
*/
|
|
27
|
+
label?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The position of the label.
|
|
30
|
+
*/
|
|
31
|
+
labelPosition?: 'top' | 'left';
|
|
32
|
+
/**
|
|
33
|
+
* The helper text for the text field.
|
|
34
|
+
*/
|
|
35
|
+
helperText?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Optional leading icon to render inside the field.
|
|
38
|
+
*/
|
|
39
|
+
iconLeft?: IconProps | IconProps['name'];
|
|
40
|
+
/**
|
|
41
|
+
* The radius of the textfield.
|
|
42
|
+
*/
|
|
43
|
+
radius?: 'small' | 'medium' | 'large' | 'full';
|
|
44
|
+
/**
|
|
45
|
+
* Whether the text field should take up the full width of its container.
|
|
46
|
+
*/
|
|
47
|
+
fullWidth?: boolean;
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TextField';
|