rouse-ui-kit 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.
@@ -0,0 +1,205 @@
1
+ import CheckIcon from "@mui/icons-material/Check";
2
+ import SearchIcon from "@mui/icons-material/Search";
3
+ import {
4
+ Box,
5
+ Checkbox,
6
+ InputAdornment,
7
+ List,
8
+ ListItemButton,
9
+ OutlinedInput,
10
+ Paper,
11
+ Typography,
12
+ } from "@mui/material";
13
+
14
+ export type MenuOption = { value: string; label: string };
15
+
16
+ type MenuProps = {
17
+ options?: MenuOption[];
18
+ selectedValues?: string[];
19
+ search?: boolean;
20
+ multiSelect?: boolean;
21
+ searchValue?: string;
22
+ onSearchChange?: (value: string) => void;
23
+ onSelect?: (value: string) => void;
24
+ onSelectAll?: () => void;
25
+ onClear?: () => void;
26
+ };
27
+
28
+ const scrollbarSx = {
29
+ overflowY: "auto",
30
+ // custom scrollbar matching Figma: 6px thumb, #dedede track border
31
+ "&::-webkit-scrollbar": { width: "14px" },
32
+ "&::-webkit-scrollbar-track": {
33
+ background: "white",
34
+ borderLeft: "1px solid #dedede",
35
+ },
36
+ "&::-webkit-scrollbar-thumb": {
37
+ backgroundColor: "#666666",
38
+ borderRadius: "4px",
39
+ border: "4px solid white",
40
+ backgroundClip: "content-box",
41
+ },
42
+ };
43
+
44
+ export default function Menu({
45
+ options = [],
46
+ selectedValues = [],
47
+ search = false,
48
+ multiSelect = false,
49
+ searchValue = "",
50
+ onSearchChange,
51
+ onSelect,
52
+ onSelectAll,
53
+ onClear,
54
+ }: MenuProps) {
55
+ return (
56
+ <Paper
57
+ elevation={8}
58
+ sx={{
59
+ width: 320,
60
+ height: 300,
61
+ borderRadius: "4px",
62
+ overflow: "hidden",
63
+ display: "flex",
64
+ bgcolor: "white",
65
+ }}
66
+ >
67
+ <Box sx={{ flex: 1, ...scrollbarSx }}>
68
+ <List disablePadding sx={{ py: "8px" }}>
69
+
70
+ {/* Search field */}
71
+ {search && (
72
+ <Box sx={{ px: "16px", py: "8px" }}>
73
+ <OutlinedInput
74
+ fullWidth
75
+ placeholder="Search"
76
+ value={searchValue}
77
+ onChange={(e) => onSearchChange?.(e.target.value)}
78
+ endAdornment={
79
+ <InputAdornment position="end">
80
+ <SearchIcon sx={{ fontSize: 20, color: "#666666" }} />
81
+ </InputAdornment>
82
+ }
83
+ sx={{
84
+ height: 32,
85
+ fontSize: 14,
86
+ letterSpacing: "0.15px",
87
+ color: "#666666",
88
+ "& .MuiOutlinedInput-notchedOutline": { borderColor: "#999999" },
89
+ "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: "#4c4c4c" },
90
+ "& .MuiOutlinedInput-input": { padding: "4px 14px" },
91
+ }}
92
+ />
93
+ </Box>
94
+ )}
95
+
96
+ {/* Select All / Clear */}
97
+ {multiSelect && (
98
+ <Box
99
+ sx={{
100
+ px: "16px",
101
+ py: "6px",
102
+ display: "flex",
103
+ justifyContent: "space-between",
104
+ }}
105
+ >
106
+ <Typography
107
+ component="span"
108
+ onClick={onSelectAll}
109
+ sx={{
110
+ fontSize: 16,
111
+ lineHeight: 1.5,
112
+ letterSpacing: "0.15px",
113
+ color: "#191919",
114
+ textDecoration: "underline",
115
+ cursor: "pointer",
116
+ userSelect: "none",
117
+ }}
118
+ >
119
+ Select All
120
+ </Typography>
121
+ <Typography
122
+ component="span"
123
+ onClick={onClear}
124
+ sx={{
125
+ fontSize: 16,
126
+ lineHeight: 1.5,
127
+ letterSpacing: "0.15px",
128
+ color: "#4c4c4c",
129
+ textDecoration: "underline",
130
+ cursor: "pointer",
131
+ userSelect: "none",
132
+ }}
133
+ >
134
+ Clear
135
+ </Typography>
136
+ </Box>
137
+ )}
138
+
139
+ {/* Menu items */}
140
+ {options.map((opt) => {
141
+ const isSelected = selectedValues.includes(opt.value);
142
+ return (
143
+ <ListItemButton
144
+ key={opt.value}
145
+ onClick={() => onSelect?.(opt.value)}
146
+ selected={isSelected && !multiSelect}
147
+ sx={{
148
+ px: "16px",
149
+ py: "6px",
150
+ minHeight: 36,
151
+ gap: 0,
152
+ "&.Mui-selected": {
153
+ bgcolor: "rgba(0, 0, 0, 0.08)", // --light/light/selected
154
+ "&:hover": { bgcolor: "rgba(0, 0, 0, 0.08)" },
155
+ },
156
+ "&:hover": { bgcolor: "rgba(0, 0, 0, 0.04)" },
157
+ }}
158
+ >
159
+ {/* Checkbox mode */}
160
+ {multiSelect && (
161
+ <Checkbox
162
+ checked={isSelected}
163
+ size="small"
164
+ tabIndex={-1}
165
+ disableRipple
166
+ sx={{
167
+ padding: "8px",
168
+ mr: "0px",
169
+ ml: "-8px",
170
+ color: "#4c4c4c",
171
+ "&.Mui-checked": { color: "#4c4c4c" },
172
+ }}
173
+ />
174
+ )}
175
+
176
+ {/* Label */}
177
+ <Typography
178
+ sx={{
179
+ flex: 1,
180
+ fontSize: 16,
181
+ fontWeight: 400,
182
+ lineHeight: 1.5,
183
+ letterSpacing: "0.15px",
184
+ color: "#191919",
185
+ whiteSpace: "nowrap",
186
+ overflow: "hidden",
187
+ textOverflow: "ellipsis",
188
+ }}
189
+ >
190
+ {opt.label}
191
+ </Typography>
192
+
193
+ {/* Check icon for single-select selected state */}
194
+ {!multiSelect && isSelected && (
195
+ <CheckIcon sx={{ fontSize: 20, color: "#4c4c4c", ml: "8px" }} />
196
+ )}
197
+ </ListItemButton>
198
+ );
199
+ })}
200
+
201
+ </List>
202
+ </Box>
203
+ </Paper>
204
+ );
205
+ }
@@ -0,0 +1,2 @@
1
+ export { default as Menu } from "./Menu";
2
+ export type { MenuOption } from "./Menu";
@@ -0,0 +1,77 @@
1
+ import CancelIcon from "@mui/icons-material/Cancel";
2
+ import {
3
+ FormControl,
4
+ FormHelperText,
5
+ IconButton,
6
+ InputAdornment,
7
+ InputLabel,
8
+ MenuItem,
9
+ Select,
10
+ type SelectChangeEvent,
11
+ } from "@mui/material";
12
+
13
+ type SelectOption = { value: string; label: string };
14
+
15
+ type SelectOutlinedProps = {
16
+ label?: string;
17
+ value?: string;
18
+ options?: SelectOption[];
19
+ helperText?: string;
20
+ error?: boolean;
21
+ disabled?: boolean;
22
+ clearable?: boolean;
23
+ onChange?: (value: string) => void;
24
+ onClear?: () => void;
25
+ };
26
+
27
+ export default function SelectOutlined({
28
+ label = "Label",
29
+ value = "",
30
+ options = [],
31
+ helperText,
32
+ error = false,
33
+ disabled = false,
34
+ clearable = false,
35
+ onChange,
36
+ onClear,
37
+ }: SelectOutlinedProps) {
38
+ const showClear = clearable && !!value;
39
+
40
+ const handleChange = (event: SelectChangeEvent) => {
41
+ onChange?.(event.target.value);
42
+ };
43
+
44
+ return (
45
+ <FormControl variant="outlined" error={error} disabled={disabled} fullWidth>
46
+ <InputLabel>{label}</InputLabel>
47
+ <Select
48
+ value={value}
49
+ label={label}
50
+ onChange={handleChange}
51
+ endAdornment={
52
+ showClear ? (
53
+ <InputAdornment position="end" sx={{ mr: "4px" }}>
54
+ <IconButton
55
+ size="small"
56
+ onClick={onClear}
57
+ tabIndex={-1}
58
+ sx={{ padding: "2px", color: "#666666" }}
59
+ >
60
+ <CancelIcon sx={{ fontSize: 20 }} />
61
+ </IconButton>
62
+ </InputAdornment>
63
+ ) : undefined
64
+ }
65
+ // Make room for the clear button before the dropdown arrow
66
+ sx={showClear ? { "& .MuiSelect-select": { paddingRight: "60px !important" } } : undefined}
67
+ >
68
+ {options.map((opt) => (
69
+ <MenuItem key={opt.value} value={opt.value}>
70
+ {opt.label}
71
+ </MenuItem>
72
+ ))}
73
+ </Select>
74
+ {helperText && <FormHelperText>{helperText}</FormHelperText>}
75
+ </FormControl>
76
+ );
77
+ }
@@ -0,0 +1 @@
1
+ export { default as SelectOutlined } from "./SelectOutlined";
@@ -0,0 +1,104 @@
1
+ import CancelIcon from "@mui/icons-material/Cancel";
2
+ import { IconButton, InputAdornment, TextField } from "@mui/material";
3
+ import type { ElementType } from "react";
4
+
5
+ type TextFieldOutlinedProps = {
6
+ label?: string;
7
+ value?: string;
8
+ helperText?: string;
9
+ error?: boolean;
10
+ disabled?: boolean;
11
+ clearable?: boolean;
12
+ icon?: ElementType;
13
+ type?: string;
14
+ onChange?: (value: string) => void;
15
+ onClear?: () => void;
16
+ onIconClick?: () => void;
17
+ };
18
+
19
+ // Border selectors self-contained here — resting color is #999 (--light/gray/40),
20
+ // different from SelectOutlined which uses rgba(0,0,0,0.23).
21
+ const borderSx = {
22
+ "& .MuiOutlinedInput-root": {
23
+ "& .MuiOutlinedInput-notchedOutline": {
24
+ borderColor: "#999999", // --light/gray/40
25
+ },
26
+ "&:hover .MuiOutlinedInput-notchedOutline": {
27
+ borderColor: "#4c4c4c", // --light/primary/main
28
+ },
29
+ "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
30
+ borderColor: "#4c4c4c",
31
+ borderWidth: "2px",
32
+ },
33
+ "&.Mui-error .MuiOutlinedInput-notchedOutline": {
34
+ borderColor: "#d32f2f", // --light/error/main
35
+ borderWidth: "2px",
36
+ },
37
+ "&.Mui-disabled .MuiOutlinedInput-notchedOutline": {
38
+ borderColor: "rgba(0, 0, 0, 0.12)", // --light/other/divider
39
+ },
40
+ },
41
+ };
42
+
43
+ export default function TextFieldOutlined({
44
+ label = "Label",
45
+ value = "",
46
+ helperText,
47
+ error = false,
48
+ disabled = false,
49
+ clearable = false,
50
+ icon: Icon,
51
+ type = "text",
52
+ onChange,
53
+ onClear,
54
+ onIconClick,
55
+ }: TextFieldOutlinedProps) {
56
+ const showClear = clearable && !!value;
57
+
58
+ const endAdornment =
59
+ showClear || Icon ? (
60
+ <>
61
+ {showClear && (
62
+ <InputAdornment position="end">
63
+ <IconButton
64
+ size="small"
65
+ onClick={onClear}
66
+ tabIndex={-1}
67
+ sx={{ padding: "2px", color: "#666666" }}
68
+ >
69
+ <CancelIcon sx={{ fontSize: 20 }} />
70
+ </IconButton>
71
+ </InputAdornment>
72
+ )}
73
+ {Icon && (
74
+ <InputAdornment position="end">
75
+ {onIconClick ? (
76
+ <IconButton size="small" onClick={onIconClick} tabIndex={-1} edge="end">
77
+ <Icon sx={{ fontSize: 24, color: "#666666" }} />
78
+ </IconButton>
79
+ ) : (
80
+ <Icon sx={{ fontSize: 24, color: "#666666" }} />
81
+ )}
82
+ </InputAdornment>
83
+ )}
84
+ </>
85
+ ) : undefined;
86
+
87
+ return (
88
+ <TextField
89
+ variant="outlined"
90
+ label={label}
91
+ value={value}
92
+ helperText={helperText}
93
+ error={error}
94
+ disabled={disabled}
95
+ type={type}
96
+ fullWidth
97
+ onChange={(e) => onChange?.(e.target.value)}
98
+ slotProps={{
99
+ input: { endAdornment },
100
+ }}
101
+ sx={borderSx}
102
+ />
103
+ );
104
+ }
@@ -0,0 +1 @@
1
+ export { default as TextFieldOutlined } from "./TextFieldOutlined";
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Rouse Design System — Design Tokens
3
+ * Source: Rouse Design System Figma library
4
+ * Reflects all --light/* CSS variables used across components.
5
+ */
6
+
7
+ // ─── Primary ──────────────────────────────────────────────────────────────────
8
+ export const primary = {
9
+ lighter: "#d9d9d9",
10
+ light: "#808080",
11
+ main: "#4c4c4c",
12
+ dark: "#191919",
13
+ tint: "#f1f1f1",
14
+ shade4p: "rgba(76, 76, 76, 0.04)",
15
+ shade8p: "rgba(76, 76, 76, 0.08)",
16
+ shade12p: "rgba(76, 76, 76, 0.12)",
17
+ shade30p: "rgba(76, 76, 76, 0.30)",
18
+ shade50p: "rgba(76, 76, 76, 0.50)",
19
+ contrastText: "#ffffff",
20
+ } as const;
21
+
22
+ // ─── Secondary ────────────────────────────────────────────────────────────────
23
+ export const secondary = {
24
+ light: "#6f8de3",
25
+ main: "#2c4d9e",
26
+ dark: "#00215f",
27
+ tint: "#eef1fc",
28
+ shade4p: "rgba(44, 77, 158, 0.04)",
29
+ shade12p: "rgba(44, 77, 158, 0.12)",
30
+ contrastText: "#ffffff",
31
+ } as const;
32
+
33
+ // ─── Semantic ─────────────────────────────────────────────────────────────────
34
+ export const error = {
35
+ light: "#ef5350",
36
+ main: "#d32f2f",
37
+ tint: "#fdeaea",
38
+ } as const;
39
+
40
+ export const warning = {
41
+ lighter: "#ffd28c",
42
+ main: "#ef6c00",
43
+ tint: "#fff3e0",
44
+ } as const;
45
+
46
+ export const success = {
47
+ light: "#4caf50",
48
+ main: "#2e7d32",
49
+ tint: "#eaf5ea",
50
+ } as const;
51
+
52
+ export const info = {
53
+ light: "#03a9f4",
54
+ main: "#0288d1",
55
+ tint: "#e1f5fe",
56
+ } as const;
57
+
58
+ // ─── Text ─────────────────────────────────────────────────────────────────────
59
+ export const text = {
60
+ primary: "#191919",
61
+ secondary: "#666666",
62
+ disabled: "#999999",
63
+ } as const;
64
+
65
+ // ─── Background ───────────────────────────────────────────────────────────────
66
+ export const background = {
67
+ white: "#ffffff",
68
+ paper: "#fafafa",
69
+ default: "#f5f5f5",
70
+ } as const;
71
+
72
+ // ─── Gray scale ───────────────────────────────────────────────────────────────
73
+ export const gray = {
74
+ 1: "#f7f7f7", // --light/gray/1
75
+ 10: "#e5e5e5", // --light/gray/10
76
+ 20: "#cccccc", // --light/gray/20
77
+ 30: "#b2b2b2", // --light/gray/30
78
+ 40: "#999999", // --light/gray/40
79
+ } as const;
80
+
81
+ // ─── Interaction overlays ─────────────────────────────────────────────────────
82
+ export const interaction = {
83
+ hover: "rgba(0, 0, 0, 0.04)", // --light/light/hover
84
+ selected: "rgba(0, 0, 0, 0.08)", // --light/light/selected
85
+ focus: "#dee0ff", // --light/blue/10
86
+ focusRing: "#bbc3ff", // --light/blue/20
87
+ } as const;
88
+
89
+ // ─── Other / borders ──────────────────────────────────────────────────────────
90
+ export const other = {
91
+ outlinedBorder: "rgba(0, 0, 0, 0.23)", // --light/other/outlined-border
92
+ divider: "rgba(0, 0, 0, 0.12)", // --light/other/divider
93
+ } as const;
94
+
95
+ // ─── Typography scale ─────────────────────────────────────────────────────────
96
+ // Source: Rouse Design System (Roboto font family, MUI Material Design scale)
97
+ export const typography = {
98
+ fontFamily: "'Roboto', sans-serif",
99
+ h1: { fontSize: "6rem", fontWeight: 300, lineHeight: 1.167, letterSpacing: "-0.01563em" },
100
+ h2: { fontSize: "3.75rem", fontWeight: 300, lineHeight: 1.2, letterSpacing: "-0.00833em" },
101
+ h3: { fontSize: "3rem", fontWeight: 400, lineHeight: 1.167, letterSpacing: "0em" },
102
+ h4: { fontSize: "2.125rem",fontWeight: 400, lineHeight: 1.235, letterSpacing: "0.00735em" },
103
+ h5: { fontSize: "1.5rem", fontWeight: 400, lineHeight: 1.334, letterSpacing: "0em" },
104
+ h6: { fontSize: "1.25rem", fontWeight: 500, lineHeight: 1.6, letterSpacing: "0.0075em" },
105
+ subtitle1: { fontSize: "1rem", fontWeight: 400, lineHeight: 1.75, letterSpacing: "0.00938em" },
106
+ subtitle2: { fontSize: "0.875rem",fontWeight: 500, lineHeight: 1.57, letterSpacing: "0.00714em" },
107
+ body1: { fontSize: "1rem", fontWeight: 400, lineHeight: 1.5, letterSpacing: "0.00938em" },
108
+ body2: { fontSize: "0.875rem",fontWeight: 400, lineHeight: 1.43, letterSpacing: "0.01071em" },
109
+ // Custom text styles found in components
110
+ button: { fontSize: "1rem", fontWeight: 500, lineHeight: "26px", letterSpacing: "0.4px", textTransform: "none" as const },
111
+ caption: { fontSize: "0.75rem", fontWeight: 400, lineHeight: 1.6, letterSpacing: "0.4px" },
112
+ overline: { fontSize: "0.75rem", fontWeight: 400, lineHeight: 2.66, letterSpacing: "0.08333em", textTransform: "uppercase" as const },
113
+ // Component-specific text styles
114
+ inputText: { fontSize: "1rem", fontWeight: 400, lineHeight: "24px", letterSpacing: "0.15px" },
115
+ inputLabel: { fontSize: "0.75rem", fontWeight: 400, lineHeight: "12px", letterSpacing: "0.15px" },
116
+ menuItem: { fontSize: "1rem", fontWeight: 400, lineHeight: 1.5, letterSpacing: "0.15px" },
117
+ chip: { fontSize: "0.8125rem",fontWeight: 400, lineHeight: "18px", letterSpacing: "0.16px" },
118
+ badgeLabel: { fontSize: "0.75rem", fontWeight: 500, lineHeight: "20px", letterSpacing: "0.14px" },
119
+ helperText: { fontSize: "0.75rem", fontWeight: 400, lineHeight: 1.6, letterSpacing: "0.4px" },
120
+ } as const;
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ // Rouse UI Kit — public exports
2
+ // Import this package in Figma Make to use all components.
3
+
4
+ export { default as ButtonContained } from "./components/Button/ButtonContained";
5
+ export { default as ButtonOutlined } from "./components/Button/ButtonOutlined";
6
+ export { default as ButtonText } from "./components/Button/ButtonText";
7
+
8
+ export { default as Checkbox } from "./components/Checkbox/Checkbox";
9
+
10
+ export { default as IconButtonOutlined } from "./components/IconButton/IconButtonOutlined";
11
+
12
+ export { default as LabelFilled } from "./components/Label/LabelFilled";
13
+ export { default as LabelOutlined } from "./components/Label/LabelOutlined";
14
+
15
+ export { default as Menu } from "./components/Menu/Menu";
16
+
17
+ export { default as SelectOutlined } from "./components/Select/SelectOutlined";
18
+
19
+ export { default as TextFieldOutlined } from "./components/TextField/TextFieldOutlined";
20
+
21
+ export { default as ChipFilled } from "./components/Chip/ChipFilled";
22
+ export { default as ChipOutlined } from "./components/Chip/ChipOutlined";
23
+ export { default as ChipStatus } from "./components/Chip/ChipStatus";
24
+ export { default as ChipStatusOutlined } from "./components/Chip/ChipStatusOutlined";
25
+
26
+ export { default as theme } from "./theme";
27
+ export * from "./design-tokens";
28
+
29
+ export type { ChipColor } from "./components/Chip/ChipFilled";
30
+ export type { ChipStatusColor } from "./components/Chip/ChipStatus";
31
+ export type { LabelColor, LabelIcon } from "./components/Label/LabelFilled";
32
+ export type { MenuOption } from "./components/Menu/Menu";
package/src/main.tsx ADDED
@@ -0,0 +1,13 @@
1
+ import { ThemeProvider } from "@mui/material/styles";
2
+ import { StrictMode } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import App from "./App";
5
+ import theme from "./theme";
6
+
7
+ createRoot(document.getElementById("root")!).render(
8
+ <StrictMode>
9
+ <ThemeProvider theme={theme}>
10
+ <App />
11
+ </ThemeProvider>
12
+ </StrictMode>
13
+ );