@carrier-dpx/air-react-library 0.3.0 → 0.5.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 +43 -0
- package/package.json +10 -7
- package/src/components/Copyright/Copyright.figma.tsx +27 -0
- package/src/components/Copyright/Copyright.tsx +36 -0
- package/src/components/Copyright/index.ts +3 -0
- package/src/components/Copyright/styles.ts +8 -0
- package/src/components/Copyright/types.ts +8 -0
- package/src/components/Divider/Divider.horizontal.figma.tsx +50 -0
- package/src/components/Divider/Divider.tsx +57 -0
- package/src/components/Divider/Divider.vertical.figma.tsx +54 -0
- package/src/components/Divider/index.ts +3 -0
- package/src/components/Link/Link.figma.tsx +70 -0
- package/src/components/Link/Link.tsx +69 -0
- package/src/components/Link/index.ts +3 -0
- package/src/components/TextField/TextField.figma.tsx +76 -0
- package/src/components/TextField/TextField.tsx +253 -0
- package/src/components/TextField/index.ts +3 -0
- package/src/components/theme/constants/styleTokens.ts +37 -1
- package/src/components/types/common.ts +5 -0
- package/src/components/types/props.ts +6 -0
- package/src/components/utils/HeightUtils.ts +38 -0
- package/src/index.ts +8 -0
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,10 @@ 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)
|
|
157
|
+
- **Link** - Hyperlink component with typography variants and underline options
|
|
158
|
+
- **Divider** - Horizontal and vertical divider lines (with optional text labels)
|
|
159
|
+
- **Copyright** - Copyright notice component (automatically adds © and current year)
|
|
117
160
|
|
|
118
161
|
More components coming soon!
|
|
119
162
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carrier-dpx/air-react-library",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Air web React component library for Figma Make",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -32,19 +32,22 @@
|
|
|
32
32
|
"author": "Carrier DPX",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"react": "^18.0.0",
|
|
36
|
-
"react-dom": "^18.0.0",
|
|
37
|
-
"@mui/material": "^5.0.0",
|
|
38
35
|
"@emotion/react": "^11.0.0",
|
|
39
|
-
"@emotion/styled": "^11.0.0"
|
|
36
|
+
"@emotion/styled": "^11.0.0",
|
|
37
|
+
"@mui/material": "^5.0.0",
|
|
38
|
+
"react": "^18.0.0",
|
|
39
|
+
"react-dom": "^18.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@figma/code-connect": "^1.0.0",
|
|
43
|
-
"@mui/material": "^5.15.0",
|
|
44
42
|
"@emotion/react": "^11.11.0",
|
|
45
43
|
"@emotion/styled": "^11.11.0",
|
|
44
|
+
"@figma/code-connect": "^1.0.0",
|
|
45
|
+
"@mui/material": "^5.15.0",
|
|
46
46
|
"@types/react": "^18.0.0",
|
|
47
47
|
"@types/react-dom": "^18.0.0",
|
|
48
48
|
"typescript": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"clsx": "^2.1.1"
|
|
49
52
|
}
|
|
50
53
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma Code Connect Configuration for Copyright Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import figma from "@figma/code-connect";
|
|
6
|
+
import Copyright from "./Copyright";
|
|
7
|
+
|
|
8
|
+
figma.connect(
|
|
9
|
+
Copyright,
|
|
10
|
+
"https://www.figma.com/design/vkoHdM6rchIhH9IWetZeP0/Air--Components?node-id=37592-67050",
|
|
11
|
+
{
|
|
12
|
+
props: {
|
|
13
|
+
/**
|
|
14
|
+
* TEXT CONTENT
|
|
15
|
+
* Copyright component shows © {year} {text}
|
|
16
|
+
* The year is automatically generated, so we just need the text
|
|
17
|
+
*/
|
|
18
|
+
text: figma.string("*"),
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* CODE EXAMPLE TEMPLATE
|
|
23
|
+
* Copyright automatically adds © symbol and current year
|
|
24
|
+
*/
|
|
25
|
+
example: ({ text }) => <Copyright text={text || "Carrier"} />,
|
|
26
|
+
}
|
|
27
|
+
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FC, useMemo } from "react";
|
|
2
|
+
import { CSSObject, styled } from "@mui/material/styles";
|
|
3
|
+
import Typography from "../Typography";
|
|
4
|
+
import { CopyrightProps } from "./types";
|
|
5
|
+
import clsx from "clsx";
|
|
6
|
+
import styles from "./styles";
|
|
7
|
+
import { getSxStyles } from "../utils/styles";
|
|
8
|
+
|
|
9
|
+
const CopyrightStyled = styled(Typography)(styles);
|
|
10
|
+
const baseClassName = "copyright";
|
|
11
|
+
|
|
12
|
+
/** The Copyright pattern provides legal confirmation for time of ownership.
|
|
13
|
+
* //Default Import
|
|
14
|
+
* `import Copyright from '@carrier-io/air-react/Copyright'`
|
|
15
|
+
* //Named Import
|
|
16
|
+
* `import { Copyright } from '@carrier-io/air-react'`
|
|
17
|
+
*/
|
|
18
|
+
const Copyright: FC<CopyrightProps> = ({ text, sx, className }) => {
|
|
19
|
+
const currentYear = useMemo(() => {
|
|
20
|
+
return new Date().getFullYear();
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<CopyrightStyled
|
|
25
|
+
variant="body3"
|
|
26
|
+
sx={(theme) =>
|
|
27
|
+
({ fontSize: "12px", ...getSxStyles(theme, sx) } as CSSObject)
|
|
28
|
+
}
|
|
29
|
+
className={clsx(baseClassName, className)}
|
|
30
|
+
>
|
|
31
|
+
© {currentYear} {text}
|
|
32
|
+
</CopyrightStyled>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default Copyright;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma Code Connect Configuration for Divider Component (Horizontal)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import figma from "@figma/code-connect";
|
|
6
|
+
import Divider from "./Divider";
|
|
7
|
+
|
|
8
|
+
figma.connect(
|
|
9
|
+
Divider,
|
|
10
|
+
"https://www.figma.com/design/vkoHdM6rchIhH9IWetZeP0/Air--Components?node-id=15518-67418",
|
|
11
|
+
{
|
|
12
|
+
props: {
|
|
13
|
+
/**
|
|
14
|
+
* VARIANT MAPPING
|
|
15
|
+
*/
|
|
16
|
+
variant: figma.enum("variant", {
|
|
17
|
+
full: "full",
|
|
18
|
+
padded: "padded",
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* TEXT ALIGN MAPPING
|
|
23
|
+
*/
|
|
24
|
+
textAlign: figma.enum("textAlign", {
|
|
25
|
+
center: "center",
|
|
26
|
+
left: "left",
|
|
27
|
+
right: "right",
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* TEXT CONTENT
|
|
32
|
+
* When text boolean is true, shows Typography children
|
|
33
|
+
*/
|
|
34
|
+
children: figma.boolean("text") ? figma.children("*") : undefined,
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* CODE EXAMPLE TEMPLATE
|
|
39
|
+
* Horizontal divider (default orientation)
|
|
40
|
+
*/
|
|
41
|
+
example: ({ variant, textAlign, children }) =>
|
|
42
|
+
children ? (
|
|
43
|
+
<Divider variant={variant} textAlign={textAlign}>
|
|
44
|
+
{children}
|
|
45
|
+
</Divider>
|
|
46
|
+
) : (
|
|
47
|
+
<Divider variant={variant} />
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import MuiDivider, {
|
|
4
|
+
DividerProps as MuiDividerProps,
|
|
5
|
+
} from "@mui/material/Divider";
|
|
6
|
+
import { styled } from "@mui/material/styles";
|
|
7
|
+
|
|
8
|
+
import { styleTokens } from "../theme/constants/styleTokens";
|
|
9
|
+
|
|
10
|
+
export interface DividerProps extends Omit<MuiDividerProps, "light"> {}
|
|
11
|
+
|
|
12
|
+
declare module "@mui/material/Divider" {
|
|
13
|
+
interface DividerPropsVariantOverrides {
|
|
14
|
+
padded: true;
|
|
15
|
+
middle: false;
|
|
16
|
+
full: true;
|
|
17
|
+
fullWidth: false;
|
|
18
|
+
inset: false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The Divider component is a thin line that groups content in lists and layouts.
|
|
23
|
+
*
|
|
24
|
+
* // Default import
|
|
25
|
+
* import Divider from '@carrier-io/air-react/Divider'
|
|
26
|
+
*
|
|
27
|
+
* // Named import
|
|
28
|
+
* import { Divider } from '@carrier-io/air-react'
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const MuiDividerStyled = styled(MuiDivider)(({ theme }) => ({
|
|
32
|
+
"&.MuiDivider-padded": {
|
|
33
|
+
marginLeft: styleTokens.margin.large,
|
|
34
|
+
marginRight: styleTokens.margin.large,
|
|
35
|
+
paddingTop: "8px",
|
|
36
|
+
paddingBottom: "8px",
|
|
37
|
+
},
|
|
38
|
+
"& .MuiDivider-wrapper": {
|
|
39
|
+
paddingLeft: styleTokens.padding.xsmall,
|
|
40
|
+
paddingRight: styleTokens.padding.xsmall,
|
|
41
|
+
color: theme.palette.base?.text.primary,
|
|
42
|
+
},
|
|
43
|
+
"&.MuiDivider-root": {
|
|
44
|
+
borderColor: theme.palette.base?.divider,
|
|
45
|
+
"&:before, &:after": {
|
|
46
|
+
borderTop: `thin solid ${theme.palette.base?.divider}`,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
const Divider = forwardRef<HTMLHRElement, DividerProps>((props, ref) => {
|
|
52
|
+
return <MuiDividerStyled {...props} ref={ref} />;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
Divider.displayName = "Divider";
|
|
56
|
+
|
|
57
|
+
export default Divider;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma Code Connect Configuration for Divider Component (Vertical)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import figma from "@figma/code-connect";
|
|
6
|
+
import Divider from "./Divider";
|
|
7
|
+
|
|
8
|
+
figma.connect(
|
|
9
|
+
Divider,
|
|
10
|
+
"https://www.figma.com/design/vkoHdM6rchIhH9IWetZeP0/Air--Components?node-id=15556-68596",
|
|
11
|
+
{
|
|
12
|
+
props: {
|
|
13
|
+
/**
|
|
14
|
+
* VARIANT MAPPING
|
|
15
|
+
*/
|
|
16
|
+
variant: figma.enum("variant", {
|
|
17
|
+
full: "full",
|
|
18
|
+
padded: "padded",
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* TEXT ALIGN MAPPING
|
|
23
|
+
*/
|
|
24
|
+
textAlign: figma.enum("textAlign", {
|
|
25
|
+
center: "center",
|
|
26
|
+
left: "left",
|
|
27
|
+
right: "right",
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* TEXT CONTENT
|
|
32
|
+
* When text boolean is true, shows Typography children
|
|
33
|
+
*/
|
|
34
|
+
children: figma.boolean("text") ? figma.children("*") : undefined,
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* CODE EXAMPLE TEMPLATE
|
|
39
|
+
* Vertical divider - sets orientation="vertical"
|
|
40
|
+
*/
|
|
41
|
+
example: ({ variant, textAlign, children }) =>
|
|
42
|
+
children ? (
|
|
43
|
+
<Divider
|
|
44
|
+
orientation="vertical"
|
|
45
|
+
variant={variant}
|
|
46
|
+
textAlign={textAlign}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</Divider>
|
|
50
|
+
) : (
|
|
51
|
+
<Divider orientation="vertical" variant={variant} />
|
|
52
|
+
),
|
|
53
|
+
}
|
|
54
|
+
);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma Code Connect Configuration for Link Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import figma from "@figma/code-connect";
|
|
6
|
+
import Link from "./Link";
|
|
7
|
+
|
|
8
|
+
figma.connect(
|
|
9
|
+
Link,
|
|
10
|
+
"https://www.figma.com/design/vkoHdM6rchIhH9IWetZeP0/Air--Components?node-id=6574-50682",
|
|
11
|
+
{
|
|
12
|
+
props: {
|
|
13
|
+
/**
|
|
14
|
+
* VARIANT MAPPING
|
|
15
|
+
* Same variants as Typography component
|
|
16
|
+
*/
|
|
17
|
+
variant: figma.enum("variant", {
|
|
18
|
+
body1: "body1",
|
|
19
|
+
"body1 semibold": "body1Semibold",
|
|
20
|
+
"body1 bold": "body1Bold",
|
|
21
|
+
body2: "body2",
|
|
22
|
+
"body2 semibold": "body2Semibold",
|
|
23
|
+
"body2 bold": "body2Bold",
|
|
24
|
+
body3: "body3",
|
|
25
|
+
"body3 semibold": "body3Semibold",
|
|
26
|
+
"body3 bold": "body3Bold",
|
|
27
|
+
h1: "h1",
|
|
28
|
+
h2: "h2",
|
|
29
|
+
h3: "h3",
|
|
30
|
+
h4: "h4",
|
|
31
|
+
h5: "h5",
|
|
32
|
+
h6: "h6",
|
|
33
|
+
caps1: "caps1",
|
|
34
|
+
"caps1 bold": "caps1Bold",
|
|
35
|
+
caps2: "caps2",
|
|
36
|
+
"caps2 bold": "caps2Bold",
|
|
37
|
+
caps3: "caps3",
|
|
38
|
+
"caps3 bold": "caps3Bold",
|
|
39
|
+
caps4: "caps4",
|
|
40
|
+
"caps4 bold": "caps4Bold",
|
|
41
|
+
}),
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* UNDERLINE MAPPING
|
|
45
|
+
* Maps Figma underline style to React underline prop
|
|
46
|
+
*/
|
|
47
|
+
underline: figma.enum("underline", {
|
|
48
|
+
always: "always",
|
|
49
|
+
hover: "hover",
|
|
50
|
+
none: "none",
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* TEXT CONTENT
|
|
55
|
+
* Maps the link text from "Label" layer
|
|
56
|
+
*/
|
|
57
|
+
children: figma.children("Label"),
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* CODE EXAMPLE TEMPLATE
|
|
62
|
+
* Note: href is a runtime prop, not configured in static Figma component
|
|
63
|
+
*/
|
|
64
|
+
example: ({ variant, underline, children }) => (
|
|
65
|
+
<Link variant={variant} underline={underline} href="#">
|
|
66
|
+
{children}
|
|
67
|
+
</Link>
|
|
68
|
+
),
|
|
69
|
+
}
|
|
70
|
+
);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import MuiLink, { LinkProps as MuiLinkProps } from "@mui/material/Link";
|
|
4
|
+
import { AllSystemCSSProperties, ResponsiveStyleValue } from "@mui/system";
|
|
5
|
+
import { useTheme } from "@mui/material";
|
|
6
|
+
import { TypographyProps } from "@mui/material/Typography";
|
|
7
|
+
|
|
8
|
+
// List of variants we want to exclude
|
|
9
|
+
type ExcludedVariants =
|
|
10
|
+
| "overline"
|
|
11
|
+
| "avatarLetter"
|
|
12
|
+
| "buttonLarge"
|
|
13
|
+
| "buttonMedium"
|
|
14
|
+
| "buttonSmall"
|
|
15
|
+
| "caption"
|
|
16
|
+
| "inputLabel"
|
|
17
|
+
| "inputText1"
|
|
18
|
+
| "inputText2"
|
|
19
|
+
| "helperText"
|
|
20
|
+
| "alertTitle"
|
|
21
|
+
| "tableHeader1"
|
|
22
|
+
| "tableHeader2"
|
|
23
|
+
| "badge"
|
|
24
|
+
| "chip"
|
|
25
|
+
| "tooltip";
|
|
26
|
+
|
|
27
|
+
// Get all valid variant options from MUI's Typography component
|
|
28
|
+
type MUIValidVariants = Exclude<TypographyProps["variant"], ExcludedVariants>;
|
|
29
|
+
|
|
30
|
+
export interface LinkProps extends Omit<MuiLinkProps, "variant"> {
|
|
31
|
+
/**
|
|
32
|
+
* Specifies how to capitalize an element's text
|
|
33
|
+
*/
|
|
34
|
+
textTransform?: ResponsiveStyleValue<AllSystemCSSProperties["textTransform"]>;
|
|
35
|
+
/**
|
|
36
|
+
* The target attribute specifies where to open the linked document:
|
|
37
|
+
*/
|
|
38
|
+
target?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Applies the theme typography styles.
|
|
41
|
+
*/
|
|
42
|
+
variant?: MUIValidVariants;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The Link component allows you to easily customize anchor elements with your theme colors and typography styles.
|
|
46
|
+
*
|
|
47
|
+
* `import Link from '@carrier-io/air-react/Link'`
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
const Link = forwardRef<HTMLAnchorElement, LinkProps>(
|
|
51
|
+
({ color, variant = "body2", ...rest }, ref) => {
|
|
52
|
+
const theme = useTheme();
|
|
53
|
+
const resolvedColor = color ?? theme.palette.primary.main;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<MuiLink
|
|
57
|
+
{...rest}
|
|
58
|
+
color={resolvedColor}
|
|
59
|
+
ref={ref}
|
|
60
|
+
variant={variant}
|
|
61
|
+
sx={{ cursor: "pointer", textUnderlineOffset: "4px" }}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
Link.displayName = "Link";
|
|
68
|
+
|
|
69
|
+
export default Link;
|
|
@@ -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;
|
|
@@ -1,6 +1,42 @@
|
|
|
1
|
+
import { Theme } from "@mui/material";
|
|
2
|
+
|
|
1
3
|
export const styleTokens = {
|
|
2
4
|
borderRadius: {
|
|
5
|
+
small: "2px",
|
|
6
|
+
medium: "4px",
|
|
3
7
|
large: "8px",
|
|
4
|
-
|
|
8
|
+
xlarge: "10px",
|
|
9
|
+
none: "0px",
|
|
10
|
+
circular: "999px",
|
|
5
11
|
},
|
|
12
|
+
border: {
|
|
13
|
+
standard: (theme: Theme) =>
|
|
14
|
+
`1px solid ${theme.palette.base?.filledInput.outlinedBorder}`,
|
|
15
|
+
divider: (theme: Theme) => `1px solid ${theme.palette.base?.divider}`,
|
|
16
|
+
},
|
|
17
|
+
padding: {
|
|
18
|
+
none: "0px",
|
|
19
|
+
xsmall: "8px",
|
|
20
|
+
small: "10px",
|
|
21
|
+
medium: "12px",
|
|
22
|
+
large: "14px",
|
|
23
|
+
xlarge: "16px",
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
margin: {
|
|
27
|
+
none: "0px",
|
|
28
|
+
xsmall: "8px",
|
|
29
|
+
small: "10px",
|
|
30
|
+
medium: "12px",
|
|
31
|
+
large: "16px",
|
|
32
|
+
xlarge: "20px",
|
|
33
|
+
},
|
|
34
|
+
height: {
|
|
35
|
+
large: "20px",
|
|
36
|
+
xlarge: "24px",
|
|
37
|
+
},
|
|
38
|
+
paddingTop: {
|
|
39
|
+
large: "18px",
|
|
40
|
+
},
|
|
41
|
+
paddingItem: "4px",
|
|
6
42
|
};
|
|
@@ -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,12 @@ 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";
|
|
7
|
+
export { default as Link } from "./components/Link";
|
|
8
|
+
export type { LinkProps } from "./components/Link";
|
|
9
|
+
export { default as Divider } from "./components/Divider";
|
|
10
|
+
export type { DividerProps } from "./components/Divider";
|
|
11
|
+
export { default as Copyright } from "./components/Copyright";
|
|
12
|
+
export type { CopyrightProps } from "./components/Copyright";
|
|
5
13
|
export * from "./components/theme";
|