@carrier-dpx/air-react-library 0.3.0 → 0.4.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/README.md CHANGED
@@ -104,6 +104,45 @@ function App() {
104
104
  - **Caps**: `caps1`, `caps1Bold`, `caps2`, `caps2Bold`, `caps3`, `caps3Bold`, `caps4`, `caps4Bold`
105
105
  - All standard Material-UI Typography props
106
106
 
107
+ ### TextField Component
108
+
109
+ ```tsx
110
+ import { TextField } from '@carrier-dpx/air-react-library';
111
+
112
+ function App() {
113
+ return (
114
+ <>
115
+ <TextField
116
+ label="Email"
117
+ placeholder="Enter your email"
118
+ size="large"
119
+ showBorder
120
+ />
121
+ <TextField
122
+ label="Password"
123
+ type="password"
124
+ placeholder="Enter your password"
125
+ size="large"
126
+ error
127
+ helperText="Password is required"
128
+ />
129
+ </>
130
+ );
131
+ }
132
+ ```
133
+
134
+ #### Available TextField Props
135
+
136
+ - **size**: `xlarge`, `large` (default), `medium`, `small`
137
+ - **color**: `primary`, `error`, `success`, `warning`, `info`
138
+ - **error**: `boolean` - Shows error state
139
+ - **disabled**: `boolean` - Disables the field
140
+ - **showBorder**: `boolean` - Shows border around field
141
+ - **hideBackgroundColor**: `boolean` - Removes background color
142
+ - **characterCounter**: `boolean` - Shows character count
143
+ - **characterMax**: `number` - Max character limit
144
+ - All standard Material-UI TextField props
145
+
107
146
  ## Figma Integration
108
147
 
109
148
  This library is integrated with Figma Code Connect. When using Figma Make, components from this library will be automatically suggested and used in generated code.
@@ -114,6 +153,7 @@ Currently available components:
114
153
 
115
154
  - **Button** - Material-UI based button component with Carrier DPX design system styling
116
155
  - **Typography** - Text component with all Carrier DPX typography variants (h1-h6, body1-3, caps, etc.)
156
+ - **TextField** - Input field component with Carrier DPX styling (supports multiple sizes, colors, validation states)
117
157
 
118
158
  More components coming soon!
119
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carrier-dpx/air-react-library",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Air web React component library for Figma Make",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Figma Code Connect Configuration for TextField Component
3
+ *
4
+ * Maps Figma TextField component to React TextField component
5
+ */
6
+
7
+ import figma from "@figma/code-connect";
8
+ import TextField from "./TextField";
9
+
10
+ figma.connect(
11
+ TextField,
12
+ "https://www.figma.com/design/vkoHdM6rchIhH9IWetZeP0/Air--Components?node-id=18128-89412",
13
+ {
14
+ props: {
15
+ /**
16
+ * SIZE MAPPING
17
+ * Maps Figma size format (with px values) to React size prop
18
+ */
19
+ size: figma.enum("size", {
20
+ "xlarge-56px": "xlarge",
21
+ "large-48px": "large",
22
+ "medium-40px": "medium",
23
+ "small-32px": "small",
24
+ }),
25
+
26
+ /**
27
+ * COLOR MAPPING
28
+ * Maps Figma color variants to React color prop
29
+ */
30
+ color: figma.enum("color", {
31
+ primary: "primary",
32
+ error: "error",
33
+ success: "success",
34
+ warning: "warning",
35
+ info: "info",
36
+ }),
37
+
38
+ /**
39
+ * ERROR STATE
40
+ * Maps Figma error boolean to React error prop
41
+ */
42
+ error: figma.boolean("error"),
43
+
44
+ /**
45
+ * DISABLED STATE
46
+ * Maps Figma disabled boolean to React disabled prop
47
+ */
48
+ disabled: figma.boolean("disabled"),
49
+
50
+ /**
51
+ * SHOW BORDER
52
+ * Maps Figma showBorder boolean to React showBorder prop
53
+ */
54
+ showBorder: figma.boolean("showBorder"),
55
+ },
56
+
57
+ /**
58
+ * CODE EXAMPLE TEMPLATE
59
+ *
60
+ * Shows how TextField should be used in React code
61
+ * Note: label, placeholder, value, onChange etc. are runtime props
62
+ * that aren't configured in Figma's static component
63
+ */
64
+ example: ({ size, color, error, disabled, showBorder }) => (
65
+ <TextField
66
+ size={size}
67
+ color={color}
68
+ error={error}
69
+ disabled={disabled}
70
+ showBorder={showBorder}
71
+ label="Email"
72
+ placeholder="Enter your email"
73
+ />
74
+ ),
75
+ }
76
+ );
@@ -0,0 +1,253 @@
1
+ import { forwardRef, useState, ChangeEvent } from "react";
2
+
3
+ import MuiTextField, {
4
+ StandardTextFieldProps as MuiTextFieldProps,
5
+ } from "@mui/material/TextField";
6
+ import { InputProps } from "@mui/material/Input";
7
+ import { InputMaxHeightMap } from "../utils/HeightUtils";
8
+ import { getSxStyles } from "../utils/styles";
9
+ import { CSSObject } from "@mui/material";
10
+ import { fleetPalette } from "../theme";
11
+ import { styleTokens } from "../theme/constants/styleTokens";
12
+
13
+ declare module "@mui/material/TextField" {
14
+ interface TextFieldPropsSizeOverrides {
15
+ xlarge: true;
16
+ large: true;
17
+ }
18
+ }
19
+ export interface TextFieldInputProps
20
+ extends Omit<InputProps, "disableUnderline"> {}
21
+
22
+ export interface TextFieldProps
23
+ extends Omit<
24
+ MuiTextFieldProps,
25
+ "variant" | "margin" | "classes" | "hiddenLabel"
26
+ > {
27
+ /** Set to true to remove background color. */
28
+ hideBackgroundColor?: boolean;
29
+
30
+ /** Input Node for Child Input. */
31
+ inputSetting?: TextFieldInputProps;
32
+
33
+ // Renamed 'hiddenLabel' to hideLabel for consistency.
34
+ /**
35
+ * If `true`, the label is hidden.
36
+ * This is used to increase density for a `FilledInput`.
37
+ * Be sure to add `aria-label` to the `input` element.
38
+ * @default false
39
+ */
40
+ hideLabel?: boolean;
41
+
42
+ /** Set to true to show a border around the TextField.*/
43
+ showBorder?: boolean;
44
+
45
+ /** Toggles the counter visibility. Setting to `true` will display the counter and respect the characterMax if set. `false` will hide the counter and ignore `characterMax`.
46
+ * @default false
47
+ */
48
+ characterCounter?: boolean;
49
+
50
+ /** Sets a counter character limit while `characterCounter` is enabled. */
51
+ characterMax?: number;
52
+ }
53
+
54
+ /** The TextField component allows users to enter or edit free-form text data.
55
+ *
56
+ * `import TextField from '@carrier-io/air-react/TextField'`
57
+ */
58
+
59
+ const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
60
+ (
61
+ {
62
+ hideBackgroundColor = false,
63
+ inputSetting = {},
64
+ InputProps = {},
65
+ label,
66
+ showBorder = false,
67
+ size = "large",
68
+ type = "text",
69
+ sx = {},
70
+ hideLabel,
71
+ InputLabelProps = {},
72
+ FormHelperTextProps = {},
73
+ helperText,
74
+ characterCounter,
75
+ characterMax,
76
+ defaultValue,
77
+ onChange,
78
+ ...rest
79
+ },
80
+ ref
81
+ ) => {
82
+ const [value, setValue] = useState<string | number>(
83
+ defaultValue as string | number
84
+ );
85
+
86
+ const handleChange = (event: ChangeEvent) => {
87
+ const targetValue = (event.target as HTMLInputElement).value;
88
+ if (
89
+ characterCounter &&
90
+ characterMax &&
91
+ targetValue?.length > characterMax
92
+ ) {
93
+ return;
94
+ }
95
+ if (onChange) {
96
+ onChange(event as ChangeEvent<HTMLInputElement>);
97
+ }
98
+ setValue(targetValue);
99
+ };
100
+
101
+ const joinInputLabelProps = {
102
+ sx: {
103
+ ...(showBorder ? { paddingLeft: "2px" } : { paddingLeft: "0px" }),
104
+ "& .MuiInputLabel-asterisk": {
105
+ color: fleetPalette.base?.filledInput.required,
106
+ },
107
+ "&.Mui-focused .MuiInputLabel-asterisk": {
108
+ color: "inherit",
109
+ },
110
+
111
+ ...InputLabelProps.sx,
112
+ },
113
+ ...InputLabelProps,
114
+ };
115
+
116
+ const joinFormHelperTextProps: typeof FormHelperTextProps = {
117
+ sx: {
118
+ whiteSpace: "nowrap",
119
+ overflow: "hidden",
120
+ textOverflow: "ellipsis",
121
+ m: "2px",
122
+ pl: "2px",
123
+ ...FormHelperTextProps.sx,
124
+ },
125
+ ...FormHelperTextProps,
126
+ };
127
+
128
+ const input = {
129
+ ...InputProps,
130
+ ...inputSetting,
131
+ disableUnderline: true,
132
+ size: size,
133
+ };
134
+
135
+ const inputSx = input.sx;
136
+
137
+ input.sx = (theme) =>
138
+ ({
139
+ ...getSxStyles(theme, inputSx),
140
+ // Conditionally apply properties
141
+ ...(!rest.multiline && { height: InputMaxHeightMap[size] }),
142
+ ...(!showBorder && !rest.error && { border: "0px!important" }),
143
+ ...(hideBackgroundColor && { backgroundColor: "inherit!important" }),
144
+ borderRadius: `${styleTokens.borderRadius.large}`,
145
+ "&.MuiInputBase-root": {
146
+ "&:hover": {
147
+ backgroundColor: `${fleetPalette.base?.filledInput.backgroundHover} !important`,
148
+ },
149
+ },
150
+ } as CSSObject);
151
+
152
+ const getHelperText = () => {
153
+ return (
154
+ <>
155
+ {helperText}
156
+ {characterCounter && (
157
+ <span
158
+ style={{
159
+ float: "right",
160
+ padding: `0px 2px`,
161
+ }}
162
+ >
163
+ {value ? value?.toString().length : "0"}
164
+ {!!characterMax && `/${characterMax}`}
165
+ </span>
166
+ )}
167
+ </>
168
+ );
169
+ };
170
+
171
+ let largeSizeLabelValue: string | number;
172
+ if (size === "large") {
173
+ largeSizeLabelValue = "16px";
174
+ } else if (size === "xlarge") {
175
+ largeSizeLabelValue = "24px";
176
+ } else {
177
+ largeSizeLabelValue = 0;
178
+ }
179
+
180
+ const largeSizeLabelSX = {
181
+ "& .MuiFormLabel-root": {
182
+ lineHeight: largeSizeLabelValue,
183
+ },
184
+ };
185
+
186
+ return (
187
+ <MuiTextField
188
+ variant="filled"
189
+ type={type}
190
+ margin={size === "medium" ? "dense" : "none"}
191
+ color={rest.error ? "error" : rest.color}
192
+ hiddenLabel={size === "small" || size === "medium" || hideLabel}
193
+ InputProps={input}
194
+ InputLabelProps={joinInputLabelProps}
195
+ FormHelperTextProps={joinFormHelperTextProps}
196
+ helperText={getHelperText()}
197
+ onChange={handleChange}
198
+ value={value}
199
+ sx={(theme) =>
200
+ ({
201
+ ...getSxStyles(theme, sx),
202
+ "& .MuiInputBase-root.MuiFilledInput-root": {
203
+ borderRadius: `${styleTokens.borderRadius.large}`,
204
+ "& .MuiInputBase-input::placeholder": {
205
+ color: fleetPalette.base?.filledInput.placeholderLabel,
206
+ },
207
+ "& .MuiInputBase-input.MuiFilledInput-input::-webkit-input-placeholder":
208
+ {
209
+ opacity: "unset",
210
+ },
211
+ },
212
+
213
+ ...((size === "large" || size === "xlarge") && largeSizeLabelSX),
214
+ "& .MuiInputBase-root.Mui-focused": {
215
+ backgroundColor:
216
+ fleetPalette.base?.background.paper + " !important",
217
+ ...((rest.error === false || rest.error === undefined) && {
218
+ "&.MuiInputBase-colorPrimary": {
219
+ border: `1px solid ${theme.palette.primary.main} !important`,
220
+ },
221
+ "&.MuiInputBase-colorSecondary": {
222
+ border: `1px solid ${theme.palette.secondary.main} !important`,
223
+ },
224
+ "&.MuiInputBase-colorBase": {
225
+ border: `1px solid ${theme.palette.base?.main} !important`,
226
+ },
227
+ "&.MuiInputBase-colorWarning": {
228
+ border: `1px solid ${theme.palette.warning.main} !important`,
229
+ },
230
+ "&.MuiInputBase-colorSuccess": {
231
+ border: `1px solid ${theme.palette.success.main} !important`,
232
+ },
233
+ "&.MuiInputBase-colorInfo": {
234
+ border: `1px solid ${theme.palette.info.main} !important`,
235
+ },
236
+ "&.MuiInputBase-colorError": {
237
+ border: `1px solid ${theme.palette.error.main} !important`,
238
+ },
239
+ }),
240
+ },
241
+ } as CSSObject)
242
+ }
243
+ label={!(size === "small" || size === "medium" || hideLabel) && label}
244
+ {...rest}
245
+ ref={ref}
246
+ />
247
+ );
248
+ }
249
+ );
250
+
251
+ TextField.displayName = "TextField";
252
+
253
+ export default TextField;
@@ -0,0 +1,3 @@
1
+ export { default } from "./TextField";
2
+ export * from "./TextField";
3
+ export type { TextFieldProps, TextFieldInputProps } from "./TextField";
@@ -0,0 +1,38 @@
1
+ export const heightMap = {
2
+ small: 32,
3
+ medium: 40,
4
+ large: 48,
5
+ };
6
+
7
+ export const InputMaxHeightMap = {
8
+ small: 32,
9
+ medium: 40,
10
+ large: 48,
11
+ xlarge: 56,
12
+ };
13
+
14
+ export const InputMaxDividerHeightMap = {
15
+ small: 16,
16
+ medium: 24,
17
+ large: 32,
18
+ xlarge: 40,
19
+ };
20
+
21
+ export const AutocompleteInternalHeightMap = {
22
+ small: "26px",
23
+ medium: 40,
24
+ large: 48,
25
+ xlarge: 56,
26
+ };
27
+
28
+ export const ChipHeightMap = {
29
+ micro: 20,
30
+ xsmall: 24,
31
+ small: 32,
32
+ };
33
+
34
+ export const RangeChartHeightMap = {
35
+ xsmall: 8,
36
+ small: 16,
37
+ medium: 24,
38
+ };
package/src/index.ts CHANGED
@@ -2,4 +2,6 @@ export { default as Button } from "./components/Button";
2
2
  export type { ButtonProps } from "./components/Button";
3
3
  export { default as Typography } from "./components/Typography";
4
4
  export type { TypographyProps } from "./components/Typography";
5
+ export { default as TextField } from "./components/TextField";
6
+ export type { TextFieldProps, TextFieldInputProps } from "./components/TextField";
5
7
  export * from "./components/theme";