@homefile/components-v2 2.52.3 → 2.53.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/dist/assets/locales/en/index.json +11 -6
- package/dist/components/appBar/HomefileLogo.js +4 -4
- package/dist/components/brand/BrandLogo.d.ts +1 -0
- package/dist/components/brand/BrandLogo.js +13 -0
- package/dist/components/brand/index.d.ts +1 -0
- package/dist/components/brand/index.js +1 -0
- package/dist/components/button/Button.d.ts +33 -0
- package/dist/components/button/Button.js +30 -0
- package/dist/components/forms/dynamicForm/fields/LabeledField.js +54 -1
- package/dist/components/icons/AppleIcon.d.ts +4 -0
- package/dist/components/icons/AppleIcon.js +5 -0
- package/dist/components/icons/GoogleIcon.d.ts +3 -0
- package/dist/components/icons/GoogleIcon.js +4 -0
- package/dist/components/icons/index.d.ts +2 -0
- package/dist/components/icons/index.js +2 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/inputs/PasswordStrengthMeter.d.ts +5 -0
- package/dist/components/inputs/PasswordStrengthMeter.js +35 -0
- package/dist/components/inputs/TextInput.js +1 -1
- package/dist/components/inputs/index.d.ts +1 -0
- package/dist/components/inputs/index.js +1 -0
- package/dist/components/loaders/SkeletonBox.js +1 -1
- package/dist/components/myHomes/HomeDetailsContent.js +1 -1
- package/dist/components/onboarding/CardFooter.d.ts +1 -1
- package/dist/components/onboarding/CardFooter.js +9 -3
- package/dist/components/onboarding/Footer.js +3 -4
- package/dist/components/onboarding/Logo.js +3 -3
- package/dist/components/onboarding/pages/SignUp.d.ts +1 -1
- package/dist/components/onboarding/pages/SignUp.js +16 -12
- package/dist/helpers/SignUp.helper.d.ts +1 -0
- package/dist/helpers/SignUp.helper.js +1 -0
- package/dist/hooks/onboarding/useSignUp.js +14 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/interfaces/onboarding/CardFooter.interface.d.ts +2 -0
- package/dist/interfaces/pages/SignUp.interface.d.ts +3 -0
- package/dist/stories/foundations/ButtonAtom.stories.d.ts +11 -0
- package/dist/stories/foundations/ButtonAtom.stories.js +15 -0
- package/dist/stories/foundations/DesignTokens.stories.d.ts +13 -0
- package/dist/stories/foundations/DesignTokens.stories.js +85 -0
- package/dist/theme/colors.d.ts +2 -0
- package/dist/theme/colors.js +2 -0
- package/dist/theme/components/ButtonStyles.d.ts +82 -0
- package/dist/theme/components/ButtonStyles.js +64 -8
- package/dist/theme/components/InputStyles.js +3 -3
- package/dist/theme/components/SelectStyles.js +1 -1
- package/dist/theme/components/TextArea.js +1 -1
- package/dist/theme/components/TextStyles.js +1 -1
- package/dist/theme/index.js +70 -0
- package/dist/utils/Validations.utils.js +1 -1
- package/package.json +1 -1
|
@@ -307,9 +307,9 @@
|
|
|
307
307
|
"footer": {
|
|
308
308
|
"copyright": "HMFC, Inc. All Rights Reserved.",
|
|
309
309
|
"privacyLink": "Privacy Policy",
|
|
310
|
-
"privacyUrl": "https://homefile.com
|
|
310
|
+
"privacyUrl": "https://privacy.homefile.com",
|
|
311
311
|
"termsLink": "Terms of Service",
|
|
312
|
-
"termsUrl": "https://homefile.com
|
|
312
|
+
"termsUrl": "https://terms.homefile.com"
|
|
313
313
|
},
|
|
314
314
|
"forms": {
|
|
315
315
|
"addAll": "Add All",
|
|
@@ -328,9 +328,10 @@
|
|
|
328
328
|
"errorImageSize": "Please select a file less than 25MB",
|
|
329
329
|
"errorImageType": "File type must be .png, .jpeg, .jpg or .heic",
|
|
330
330
|
"errorLogin": "Invalid email or password",
|
|
331
|
-
"errorPasswordLength": "Password must be at least
|
|
331
|
+
"errorPasswordLength": "Password must be at least 8 characters",
|
|
332
332
|
"errorPasswordMatch": "Passwords do not match",
|
|
333
333
|
"firstName": "First name",
|
|
334
|
+
"fullName": "Full name",
|
|
334
335
|
"invalid": "Please type a valid",
|
|
335
336
|
"invalid2": "Invalid field",
|
|
336
337
|
"itemDetails": "Add Item Details",
|
|
@@ -1132,12 +1133,16 @@
|
|
|
1132
1133
|
"signupPartner": "Don’t have a Homefile Partner account?"
|
|
1133
1134
|
},
|
|
1134
1135
|
"signup": {
|
|
1135
|
-
"signin": "Already have
|
|
1136
|
+
"signin": "Already have an account?",
|
|
1136
1137
|
"signinPartner": "Already have a Homefile Partner account?",
|
|
1137
1138
|
"signinBt": "Log in",
|
|
1138
|
-
"signupBt": "Create
|
|
1139
|
+
"signupBt": "Create account",
|
|
1140
|
+
"continueApple": "Continue with Apple",
|
|
1141
|
+
"continueGoogle": "Continue with Google",
|
|
1142
|
+
"or": "or",
|
|
1143
|
+
"partnerCodePrompt": "Have a partner code?",
|
|
1139
1144
|
"subtitle": "Create a strong password",
|
|
1140
|
-
"terms": "By creating an account you agree to the
|
|
1145
|
+
"terms": "By creating an account you agree to the",
|
|
1141
1146
|
"title": "Create your Homefile Account",
|
|
1142
1147
|
"titlePartner": "Create your Homefile Partner Account"
|
|
1143
1148
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Button, Flex,
|
|
3
|
-
import {
|
|
2
|
+
import { Box, Button, Flex, Text } from '@chakra-ui/react';
|
|
3
|
+
import { BrandLogo } from '../../components/brand/BrandLogo';
|
|
4
4
|
import { useWindowDimensions } from '../../hooks';
|
|
5
5
|
import { MOBILE_WIDTH } from '../../utils';
|
|
6
6
|
import { t } from 'i18next';
|
|
@@ -8,7 +8,7 @@ export const HomefileLogo = ({ onClick, isPartner = false }) => {
|
|
|
8
8
|
const { windowDimensions: { width }, } = useWindowDimensions();
|
|
9
9
|
const isMobile = width < MOBILE_WIDTH;
|
|
10
10
|
if (!isPartner) {
|
|
11
|
-
return (_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(
|
|
11
|
+
return (_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(BrandLogo, {}) }));
|
|
12
12
|
}
|
|
13
|
-
return (_jsxs(Flex, { align: "center", gap: ['2', 'base'], children: [_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(
|
|
13
|
+
return (_jsxs(Flex, { align: "center", gap: ['2', 'base'], children: [_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(BrandLogo, {}) }), !isMobile && (_jsx(Box, { pl: "base", borderLeft: "1px solid", borderColor: "border.subtle", children: _jsx(Text, { fontSize: "lg", fontWeight: "medium", children: t('partner.admin') }) }))] }));
|
|
14
14
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BrandLogo: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Flex, Text, chakra } from '@chakra-ui/react';
|
|
3
|
+
/**
|
|
4
|
+
* BrandLogo — the ONE Homefile logo, as code (not an image).
|
|
5
|
+
*
|
|
6
|
+
* Green house mark + "Homefile" wordmark in Poppins Medium + registered mark.
|
|
7
|
+
* Self-contained and themed via Chakra, so every surface that shows the logo
|
|
8
|
+
* (app bar, onboarding, partner, etc.) renders the exact same mark. Consolidates
|
|
9
|
+
* the previously fragmented logo images/components into a single source.
|
|
10
|
+
*/
|
|
11
|
+
// House icon path (viewBox 0 0 32.877 32.877), Homefile green #6ab528.
|
|
12
|
+
const HOUSE_PATH = 'M912.815,312.316H885.7a2.882,2.882,0,0,0-2.882,2.882V342.31a2.882,2.882,0,0,0,2.882,2.882h8.473V334.22a4.936,4.936,0,0,1,9.872,0v8.58H901.77v-8.58a2.658,2.658,0,0,0-5.316,0v10.973h10.661V333.169a7.827,7.827,0,1,0-15.654,0v7.764h-2.278v-7.764c0-.287.013-.582.04-.881h-2.086a1.279,1.279,0,0,1-1.163-.692,1.073,1.073,0,0,1,.249-1.265l9.8-9.631,3.083-2.977.153-.147.153.147L902.5,320.7l9.8,9.626a1.076,1.076,0,0,1,.253,1.269,1.279,1.279,0,0,1-1.163.692h-2.028c.027.3.04.6.04.881v12.024h3.422a2.882,2.882,0,0,0,2.882-2.882V315.2A2.882,2.882,0,0,0,912.815,312.316Z';
|
|
13
|
+
export const BrandLogo = () => (_jsxs(Flex, { align: "center", gap: "2.5", children: [_jsx(chakra.svg, { width: "33", height: "33", viewBox: "882.821 312.316 32.877 32.877", role: "img", "aria-label": "Homefile", xmlns: "http://www.w3.org/2000/svg", display: "block", children: _jsx(chakra.path, { d: HOUSE_PATH, fill: "brand.logo" }) }), _jsxs(Text, { fontFamily: "primary", fontWeight: "medium", fontSize: "2xl", lineHeight: "none", letterSpacing: "-0.01em", color: "text.heading", children: ["Homefile", _jsx(Box, { as: "sup", fontSize: "0.72em", verticalAlign: "-0.08em", ml: "-1px", children: "\u00AE" })] })] }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './BrandLogo';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './BrandLogo';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ButtonProps as ChakraButtonProps } from '@chakra-ui/react';
|
|
2
|
+
/**
|
|
3
|
+
* Button — the canonical button atom.
|
|
4
|
+
*
|
|
5
|
+
* 3 templates (variant) × 2 real sizes × width/shape/colorScheme, with
|
|
6
|
+
* default/hover/active/focus/disabled states from the theme. This is the
|
|
7
|
+
* target set; the old 20 theme variants remain as a compat shim during
|
|
8
|
+
* migration. Width is a style-prop axis (w / isFullWidth), NOT baked in.
|
|
9
|
+
*/
|
|
10
|
+
export type ButtonTemplate = 'filled' | 'outline' | 'ghost';
|
|
11
|
+
export type ButtonSize = 'standard' | 'compact';
|
|
12
|
+
export type ButtonShape = 'square' | 'pillLeft' | 'pillRight';
|
|
13
|
+
export type ButtonColorScheme = 'brand' | 'success';
|
|
14
|
+
export interface ButtonProps extends Omit<ChakraButtonProps, 'variant' | 'size' | 'colorScheme'> {
|
|
15
|
+
/** fill treatment — the one real archetype axis */
|
|
16
|
+
variant?: ButtonTemplate;
|
|
17
|
+
/** real height: standard 3.4rem · compact 2.5rem */
|
|
18
|
+
size?: ButtonSize;
|
|
19
|
+
colorScheme?: ButtonColorScheme;
|
|
20
|
+
/** corner treatment — square (brand default) or a pill half for segmented pairs */
|
|
21
|
+
shape?: ButtonShape;
|
|
22
|
+
/** square icon-only button (sized to the height, no h-padding) */
|
|
23
|
+
isIcon?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare const Button: import("@chakra-ui/react").ComponentWithAs<"button", ButtonProps>;
|
|
26
|
+
/**
|
|
27
|
+
* IconButton — the most-used derivative (~60 real usages). A square,
|
|
28
|
+
* icon-only Button. `aria-label` is required (accessibility).
|
|
29
|
+
*/
|
|
30
|
+
export interface IconButtonProps extends Omit<ButtonProps, 'isIcon'> {
|
|
31
|
+
'aria-label': string;
|
|
32
|
+
}
|
|
33
|
+
export declare const IconButton: import("@chakra-ui/react").ComponentWithAs<"button", IconButtonProps>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { Button as ChakraButton, forwardRef } from '@chakra-ui/react';
|
|
14
|
+
const shapeStyles = (shape) => {
|
|
15
|
+
if (shape === 'pillLeft')
|
|
16
|
+
return { borderLeftRadius: 'full', borderRightRadius: 'none' };
|
|
17
|
+
if (shape === 'pillRight')
|
|
18
|
+
return { borderRightRadius: 'full', borderLeftRadius: 'none' };
|
|
19
|
+
return {}; // square → the variant's own radius (none)
|
|
20
|
+
};
|
|
21
|
+
export const Button = forwardRef((_a, ref) => {
|
|
22
|
+
var { variant = 'filled', size = 'standard', colorScheme = 'brand', shape = 'square', isIcon = false } = _a, rest = __rest(_a, ["variant", "size", "colorScheme", "shape", "isIcon"]);
|
|
23
|
+
const iconProps = isIcon
|
|
24
|
+
? { boxSize: size === 'compact' ? '2.5rem' : '3rem', minW: 0, px: 0 }
|
|
25
|
+
: {};
|
|
26
|
+
return (_jsx(ChakraButton, Object.assign({ ref: ref, variant: variant, size: size, colorScheme: colorScheme }, shapeStyles(shape), iconProps, rest)));
|
|
27
|
+
});
|
|
28
|
+
Button.displayName = 'Button';
|
|
29
|
+
export const IconButton = forwardRef((props, ref) => (_jsx(Button, Object.assign({ ref: ref, isIcon: true, variant: "ghost" }, props))));
|
|
30
|
+
IconButton.displayName = 'IconButton';
|
|
@@ -1,8 +1,61 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
3
|
import { Box, Text, Flex } from '@chakra-ui/react';
|
|
3
4
|
export const LabeledField = ({ label, isFilled = false, children, }) => {
|
|
5
|
+
const wrapRef = useRef(null);
|
|
6
|
+
const [autofilled, setAutofilled] = useState(false);
|
|
7
|
+
// Chrome fills the DOM input on load without firing React's onChange, so the
|
|
8
|
+
// parent's `isFilled` (derived from React state) stays false and the label
|
|
9
|
+
// never floats. Detect the browser autofill directly via the `:-webkit-autofill`
|
|
10
|
+
// pseudo: on mount + a couple of short delays (autofill lands just after mount),
|
|
11
|
+
// and on Chrome's autofill animation event (covers a login picked from the
|
|
12
|
+
// saved-credentials dropdown, which also fires no onChange).
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const node = wrapRef.current;
|
|
15
|
+
if (!node)
|
|
16
|
+
return;
|
|
17
|
+
const check = () => {
|
|
18
|
+
const input = node.querySelector('input');
|
|
19
|
+
try {
|
|
20
|
+
setAutofilled(!!input && input.matches(':-webkit-autofill'));
|
|
21
|
+
}
|
|
22
|
+
catch (_a) {
|
|
23
|
+
// pseudo unsupported (non-Chromium) — fall back to React state only
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
check();
|
|
27
|
+
const timers = [
|
|
28
|
+
window.setTimeout(check, 80),
|
|
29
|
+
window.setTimeout(check, 400),
|
|
30
|
+
];
|
|
31
|
+
const onAnim = (e) => {
|
|
32
|
+
var _a;
|
|
33
|
+
if ((_a = e.animationName) === null || _a === void 0 ? void 0 : _a.startsWith('hfAutofill'))
|
|
34
|
+
check();
|
|
35
|
+
};
|
|
36
|
+
node.addEventListener('animationstart', onAnim, true);
|
|
37
|
+
return () => {
|
|
38
|
+
timers.forEach(clearTimeout);
|
|
39
|
+
node.removeEventListener('animationstart', onAnim, true);
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
4
42
|
if (!label) {
|
|
5
43
|
return _jsx(_Fragment, { children: children });
|
|
6
44
|
}
|
|
7
|
-
|
|
45
|
+
const showLabel = isFilled || autofilled;
|
|
46
|
+
return (_jsxs(Box, { ref: wrapRef, w: "100%", sx: {
|
|
47
|
+
// Emit a no-op animation on autofill state changes so `animationstart`
|
|
48
|
+
// fires and re-checks the pseudo. This is how autofill is observable
|
|
49
|
+
// from JS (the pseudo alone gives no event).
|
|
50
|
+
'& input:-webkit-autofill': {
|
|
51
|
+
animationName: 'hfAutofillStart',
|
|
52
|
+
animationDuration: '0.001s',
|
|
53
|
+
},
|
|
54
|
+
'& input:not(:-webkit-autofill)': {
|
|
55
|
+
animationName: 'hfAutofillCancel',
|
|
56
|
+
animationDuration: '0.001s',
|
|
57
|
+
},
|
|
58
|
+
'@keyframes hfAutofillStart': { from: {}, to: {} },
|
|
59
|
+
'@keyframes hfAutofillCancel': { from: {}, to: {} },
|
|
60
|
+
}, children: [showLabel && (_jsx(Flex, { gap: "1", align: "start", children: _jsx(Text, { fontSize: "xs", fontFamily: "secondary", color: "gray.2", mb: "0.5", children: label }) })), children] }));
|
|
8
61
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { colors } from '../../theme/colors';
|
|
3
|
+
export const AppleIcon = ({ size = 20, fill = colors.gray[4] }) => {
|
|
4
|
+
return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 384 512", fill: fill, "aria-hidden": "true", children: _jsx("path", { d: "M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" }) }));
|
|
5
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export const GoogleIcon = ({ size = 20 }) => {
|
|
3
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 48 48", "aria-hidden": "true", children: [_jsx("path", { fill: "#EA4335", d: "M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" }), _jsx("path", { fill: "#4285F4", d: "M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" }), _jsx("path", { fill: "#FBBC05", d: "M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" }), _jsx("path", { fill: "#34A853", d: "M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" })] }));
|
|
4
|
+
};
|
package/dist/components/index.js
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Flex, Text } from '@chakra-ui/react';
|
|
3
|
+
/**
|
|
4
|
+
* PasswordStrengthMeter — lightweight, dependency-free strength hint.
|
|
5
|
+
*
|
|
6
|
+
* This is UX GUIDANCE ONLY (client-side, bypassable). Real strength/breach
|
|
7
|
+
* enforcement belongs server-side — tracked separately (breach-check ticket).
|
|
8
|
+
*/
|
|
9
|
+
const LEVELS = [
|
|
10
|
+
{ label: '', color: 'lightGray.1' }, // 0 — unused (empty returns null)
|
|
11
|
+
{ label: 'Weak', color: 'red.1' },
|
|
12
|
+
{ label: 'Fair', color: 'orange.2' },
|
|
13
|
+
{ label: 'Good', color: 'blue.2' },
|
|
14
|
+
{ label: 'Strong', color: 'status.success' },
|
|
15
|
+
];
|
|
16
|
+
const scorePassword = (pw) => {
|
|
17
|
+
if (!pw)
|
|
18
|
+
return 0;
|
|
19
|
+
let score = 1; // any input is at least "Weak"
|
|
20
|
+
if (pw.length >= 8)
|
|
21
|
+
score++;
|
|
22
|
+
if (pw.length >= 12)
|
|
23
|
+
score++;
|
|
24
|
+
const variety = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^a-zA-Z0-9]/].filter((re) => re.test(pw)).length;
|
|
25
|
+
if (variety >= 3)
|
|
26
|
+
score++;
|
|
27
|
+
return Math.min(score, 4);
|
|
28
|
+
};
|
|
29
|
+
export const PasswordStrengthMeter = ({ password = '', }) => {
|
|
30
|
+
if (!password)
|
|
31
|
+
return null;
|
|
32
|
+
const score = scorePassword(password);
|
|
33
|
+
const level = LEVELS[score];
|
|
34
|
+
return (_jsxs(Box, { mt: "1.5", "aria-live": "polite", children: [_jsx(Flex, { gap: "1", mb: "1", children: [1, 2, 3, 4].map((i) => (_jsx(Box, { flex: "1", h: "4px", borderRadius: "full", bg: i <= score ? level.color : 'lightGray.1', transition: "background-color 0.2s ease" }, i))) }), _jsx(Text, { fontSize: "xxs", fontFamily: "secondary", color: level.color, textAlign: "right", children: level.label })] }));
|
|
35
|
+
};
|
|
@@ -18,5 +18,5 @@ export const TextInput = (_a) => {
|
|
|
18
18
|
const error = hasError && !isDisabled;
|
|
19
19
|
const isFilled = String(value).trim().length > 0 && showLabel;
|
|
20
20
|
const placeholderToUse = isFilled ? '' : ((_b = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _b !== void 0 ? _b : '');
|
|
21
|
-
return (_jsxs(FormControl, { isInvalid: error, children: [_jsx(LabeledField, { label: label !== null && label !== void 0 ? label : placeholder, isFilled: isFilled, children: _jsx(Input, Object.assign({}, props, { autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, id: id, placeholder: placeholderToUse, value: value, type: type, onChange: handleChange, isInvalid: error, isDisabled: isDisabled
|
|
21
|
+
return (_jsxs(FormControl, { isInvalid: error, children: [_jsx(LabeledField, { label: label !== null && label !== void 0 ? label : placeholder, isFilled: isFilled, children: _jsx(Input, Object.assign({}, props, { autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, id: id, placeholder: placeholderToUse, value: value, type: type, onChange: handleChange, isInvalid: error, isDisabled: isDisabled })) }), error && (_jsx(Text, { variant: "error", my: "1", children: errorMessage })), hint && !error && (_jsx(Text, { variant: "hint", my: "1", children: hint }))] }));
|
|
22
22
|
};
|
|
@@ -5,6 +5,7 @@ export * from './EditFileUploader';
|
|
|
5
5
|
export * from './ItemSubTypeSelect';
|
|
6
6
|
export * from './MobileFileUploader';
|
|
7
7
|
export * from './PasswordInput';
|
|
8
|
+
export * from './PasswordStrengthMeter';
|
|
8
9
|
export * from './RadioCard';
|
|
9
10
|
export * from './SearchInput';
|
|
10
11
|
export * from './SelectButton';
|
|
@@ -5,6 +5,7 @@ export * from './EditFileUploader';
|
|
|
5
5
|
export * from './ItemSubTypeSelect';
|
|
6
6
|
export * from './MobileFileUploader';
|
|
7
7
|
export * from './PasswordInput';
|
|
8
|
+
export * from './PasswordStrengthMeter';
|
|
8
9
|
export * from './RadioCard';
|
|
9
10
|
export * from './SearchInput';
|
|
10
11
|
export * from './SelectButton';
|
|
@@ -9,4 +9,4 @@ const shine = keyframes `
|
|
|
9
9
|
background-position: 200% 0;
|
|
10
10
|
}
|
|
11
11
|
`;
|
|
12
|
-
export const SkeletonBox = ({ height = '20px', width = '100%', borderRadius = 'md', }) => (_jsx(Box, { bg: "
|
|
12
|
+
export const SkeletonBox = ({ height = '20px', width = '100%', borderRadius = 'md', }) => (_jsx(Box, { bg: "bg.surface", w: width, h: height, borderRadius: borderRadius, backgroundImage: "linear-gradient(90deg, #F4F7F8 0%, #e6ecee 50%, #F4F7F8 100%)", backgroundSize: "200% 100%", animation: `${shine} 1.5s infinite linear` }));
|
|
@@ -17,5 +17,5 @@ import { House } from '../../assets/images';
|
|
|
17
17
|
export const HomeDetailsContent = (_a) => {
|
|
18
18
|
var props = __rest(_a, []);
|
|
19
19
|
const { handleClose, isLoading, children, showFooter, footerButtons } = props;
|
|
20
|
-
return (_jsxs(DrawerContent, { children: [_jsx(DrawerHeader, { p: "0", children: _jsx(PanelHeader, { icon: House, handleCloseButton: handleClose, title: t("myHomes.details.header") }) }), _jsx(DrawerBody, { padding: "0 0 6rem 0", bg: "
|
|
20
|
+
return (_jsxs(DrawerContent, { children: [_jsx(DrawerHeader, { p: "0", children: _jsx(PanelHeader, { icon: House, handleCloseButton: handleClose, title: t("myHomes.details.header") }) }), _jsx(DrawerBody, { padding: "0 0 6rem 0", bg: "bg.surface", children: _jsx(Box, { children: isLoading ? (_jsx(Loading, {})) : (_jsxs(_Fragment, { children: [_jsx(HomeCard, Object.assign({}, props)), children] })) }) }), _jsx(DrawerFooter, { children: _jsx(FooterDrawer, { isOpen: showFooter, children: footerButtons }) })] }));
|
|
21
21
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { CardFooterI } from '../../interfaces';
|
|
2
|
-
export declare const CardFooter: ({ title, buttonLabel, handleButtonClick, }: CardFooterI) => import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare const CardFooter: ({ title, buttonLabel, handleButtonClick, children, }: CardFooterI) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Container, Text
|
|
3
|
-
export const CardFooter = ({ title = '', buttonLabel, handleButtonClick, }) => {
|
|
4
|
-
return (_jsx(Container, { variant: "secondary", centerContent: true, children: _jsxs(Box, { w: "full", px: ['container.sm', 'container.md', 'container.lg'], my: "auto", py: "2rem", children: [
|
|
2
|
+
import { Box, Container, Text } from '@chakra-ui/react';
|
|
3
|
+
export const CardFooter = ({ title = '', buttonLabel, handleButtonClick, children, }) => {
|
|
4
|
+
return (_jsx(Container, { variant: "secondary", centerContent: true, children: _jsxs(Box, { w: "full", px: ['container.sm', 'container.md', 'container.lg'], my: "auto", py: "2rem", children: [_jsxs(Text, { textAlign: "center", variant: "body", children: [title, ' ', _jsx(Box, { as: "button", type: "button", display: "inline", color: "link.default", fontWeight: "medium", onClick: handleButtonClick, _hover: { textDecoration: 'underline' }, _focusVisible: {
|
|
5
|
+
textDecoration: 'underline',
|
|
6
|
+
outline: '2px solid',
|
|
7
|
+
outlineColor: 'border.focus',
|
|
8
|
+
outlineOffset: '2px',
|
|
9
|
+
borderRadius: 'sm',
|
|
10
|
+
}, children: buttonLabel })] }), children && _jsx(Box, { mt: 6, children: children })] }) }));
|
|
5
11
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { t } from 'i18next';
|
|
3
3
|
import { Flex, Text } from '@chakra-ui/react';
|
|
4
4
|
export const Footer = () => {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
return (_jsxs(Flex, { w: ['container.full', 'container.sm', 'container.md'], direction: ['column', 'row'], mx: "auto", my: 4, justifyContent: ['center', 'space-between'], alignItems: "center", children: [_jsx(Text, { variant: "label", children: `©${currentYear} ${t('footer.copyright')}` }), _jsxs(Flex, { gap: 2, children: [_jsx(Text, { variant: "label", children: _jsx("a", { target: "_blank", href: t('footer.termsUrl'), children: t('footer.termsLink') }) }), _jsx(Text, { variant: "label", children: _jsx("a", { target: "_blank", href: t('footer.privacyUrl'), children: t('footer.privacyLink') }) })] })] }));
|
|
5
|
+
const currentYear = new Date().getFullYear();
|
|
6
|
+
return (_jsx(Flex, { justifyContent: "center", px: 4, my: 4, children: _jsx(Text, { variant: "label", textAlign: "center", children: `©${currentYear} ${t('footer.copyright')}` }) }));
|
|
8
7
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Center, Container
|
|
3
|
-
import {
|
|
2
|
+
import { Center, Container } from '@chakra-ui/react';
|
|
3
|
+
import { BrandLogo } from '../../components/brand/BrandLogo';
|
|
4
4
|
export const Logo = () => {
|
|
5
|
-
return (_jsx(Container, { variant: "ghost", size: "full", children: _jsx(Center, { h: "container.xxs", children: _jsx(
|
|
5
|
+
return (_jsx(Container, { variant: "ghost", size: "full", children: _jsx(Center, { h: "container.xxs", children: _jsx(BrandLogo, {}) }) }));
|
|
6
6
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { SignUpI } from '../../../interfaces';
|
|
2
|
-
export declare const SignUp: ({ isLoading, isPartner, handleCreateAccount, handleSignIn,
|
|
2
|
+
export declare const SignUp: ({ isLoading, isPartner, handleCreateAccount, handleSignIn, handleGoogleSignIn, handleAppleSignIn, signupErrorMessage, values, }: SignUpI) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { t } from 'i18next';
|
|
3
|
-
import { Box, Button, Center, Container, Stack, Text } from '@chakra-ui/react';
|
|
4
|
-
import { Footer, CardFooter, PageTitle, PasswordInput, TextInput, ButtonLoader, BasePageWrapper, AuthHeader, } from '../../../components';
|
|
5
|
-
import { isEmptyField, isValidEmail, isValidPassword
|
|
4
|
+
import { Box, Button, Center, Container, Divider, Flex, Stack, Text, } from '@chakra-ui/react';
|
|
5
|
+
import { Footer, CardFooter, PageTitle, PasswordInput, PasswordStrengthMeter, TextInput, ButtonLoader, BasePageWrapper, AuthHeader, GoogleIcon, AppleIcon, } from '../../../components';
|
|
6
|
+
import { isEmptyField, isValidEmail, isValidPassword } from '../../../utils';
|
|
6
7
|
import { useSignUp } from '../../../hooks';
|
|
7
|
-
export const SignUp = ({ isLoading, isPartner, handleCreateAccount, handleSignIn,
|
|
8
|
+
export const SignUp = ({ isLoading, isPartner, handleCreateAccount, handleSignIn, handleGoogleSignIn, handleAppleSignIn, signupErrorMessage, values = {
|
|
8
9
|
email: '',
|
|
9
10
|
firstName: '',
|
|
10
11
|
lastName: '',
|
|
@@ -12,15 +13,18 @@ export const SignUp = ({ isLoading, isPartner, handleCreateAccount, handleSignIn
|
|
|
12
13
|
password: '',
|
|
13
14
|
partnerCode: '',
|
|
14
15
|
}, }) => {
|
|
15
|
-
var _a, _b, _c;
|
|
16
|
+
var _a, _b, _c, _d;
|
|
16
17
|
const { inputs, handleInputChange, handleSubmit, isValidated, passwordErrorMessage, } = useSignUp({
|
|
17
18
|
handleCreateAccount,
|
|
18
19
|
values,
|
|
19
20
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// Partner code is hidden behind a "Have a partner code?" link, closed by default.
|
|
22
|
+
const [showPartnerCode, setShowPartnerCode] = useState(false);
|
|
23
|
+
// Social sign-in (and the "or" divider) appear only once OAuth is wired —
|
|
24
|
+
// i.e. a handler is actually provided by the app.
|
|
25
|
+
const showGoogle = Boolean(handleGoogleSignIn);
|
|
26
|
+
const showApple = Boolean(handleAppleSignIn);
|
|
27
|
+
const showSocial = showGoogle || showApple;
|
|
28
|
+
return (_jsxs(BasePageWrapper, { children: [_jsxs(Container, { size: "onboarding", boxShadow: "lg", children: [_jsx(AuthHeader, { isPartner: isPartner }), _jsx(PageTitle, { title: isPartner ? t('signup.titlePartner') : t('signup.title') }), _jsx(Box, { px: ['container.sm', 'container.md', 'container.lg'], mb: 6, children: _jsxs(Stack, { spacing: "base", as: "form", children: [Boolean(signupErrorMessage) && (_jsx(Text, { variant: "error", children: signupErrorMessage })), _jsx(TextInput, { errorMessage: t('forms.fullName') + ' ' + t('forms.required'), hasError: isValidated && isEmptyField(inputs.name), id: "name", placeholder: t('forms.fullName'), value: (_a = inputs.name) !== null && _a !== void 0 ? _a : '', handleChange: handleInputChange, autoComplete: "name" }), _jsx(TextInput, { autoCapitalize: "none", autoCorrect: "off", errorMessage: t('forms.email') + ' ' + t('forms.required'), hasError: isValidated &&
|
|
29
|
+
(isEmptyField(inputs.email) || !isValidEmail(inputs.email)), id: "email", placeholder: t('forms.email'), value: inputs.email, handleChange: handleInputChange, autoComplete: "email" }), _jsxs(Box, { children: [_jsx(PasswordInput, { errorMessage: passwordErrorMessage(values.password), hasError: isValidated && !isValidPassword(inputs.password), id: "password", placeholder: t('forms.password'), value: (_b = inputs.password) !== null && _b !== void 0 ? _b : '', handleChange: (event) => handleInputChange(event), autoComplete: "new-password" }), _jsx(PasswordStrengthMeter, { password: (_c = inputs.password) !== null && _c !== void 0 ? _c : '' })] }), _jsx(Center, { children: _jsx(Button, { variant: "text", leftIcon: _jsx(Text, { as: "span", fontSize: "md", lineHeight: "1", children: "+" }), onClick: () => setShowPartnerCode((prev) => !prev), children: t('signup.partnerCodePrompt') }) }), showPartnerCode && (_jsx(TextInput, { autoCapitalize: "none", autoCorrect: "off", id: "partnerCode", placeholder: t('forms.promoCode'), value: (_d = inputs.partnerCode) !== null && _d !== void 0 ? _d : '', handleChange: handleInputChange, autoComplete: "off" })), _jsx(Button, { isLoading: isLoading, spinner: _jsx(ButtonLoader, {}), onClick: handleSubmit, children: t('signup.signupBt') }), showSocial && (_jsxs(_Fragment, { children: [_jsxs(Flex, { align: "center", gap: "3", py: "1", children: [_jsx(Divider, { flex: "1", borderColor: "border.default" }), _jsx(Text, { variant: "caption", children: t('signup.or') }), _jsx(Divider, { flex: "1", borderColor: "border.default" })] }), showGoogle && (_jsx(Button, { variant: "secondary", leftIcon: _jsx(GoogleIcon, { size: 20 }), onClick: handleGoogleSignIn, children: t('signup.continueGoogle') })), showApple && (_jsx(Button, { variant: "secondary", leftIcon: _jsx(AppleIcon, { size: 20 }), onClick: handleAppleSignIn, children: t('signup.continueApple') }))] }))] }) }), _jsx(CardFooter, { title: isPartner ? t('signup.signinPartner') : t('signup.signin'), buttonLabel: t('signup.signinBt'), handleButtonClick: handleSignIn, children: _jsxs(Text, { textAlign: "center", variant: "info", w: "100%", children: [t('signup.terms'), ' ', _jsxs(Text, { as: "span", whiteSpace: "nowrap", children: ["Homefile", ' ', _jsx("a", { target: "_blank", href: t('footer.termsUrl'), children: t('footer.termsLink') }), "."] })] }) })] }), _jsx(Footer, {})] }));
|
|
26
30
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { t } from 'i18next';
|
|
3
3
|
import { SignUpForm } from '../../helpers';
|
|
4
|
-
import { isEmptyField, isValidEmail, isValidPassword
|
|
4
|
+
import { isEmptyField, isValidEmail, isValidPassword } from '../../utils';
|
|
5
5
|
export const useSignUp = ({ handleCreateAccount, values }) => {
|
|
6
6
|
const [inputs, setInputs] = useState(SignUpForm);
|
|
7
7
|
const [isValidated, setIsValidated] = useState(false);
|
|
@@ -13,16 +13,23 @@ export const useSignUp = ({ handleCreateAccount, values }) => {
|
|
|
13
13
|
setInputs(Object.assign(Object.assign({}, inputs), { [event.target.id]: event.target.value }));
|
|
14
14
|
};
|
|
15
15
|
const handleSubmit = () => {
|
|
16
|
+
var _a;
|
|
16
17
|
setIsValidated(true);
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// One "Name" field in the UI; split into first/last for the backend
|
|
19
|
+
// (contract unchanged — firstName is what greetings/emails/Homi use).
|
|
20
|
+
const name = ((_a = inputs.name) !== null && _a !== void 0 ? _a : '').trim();
|
|
21
|
+
const [firstName = '', ...rest] = name.split(/\s+/);
|
|
22
|
+
const lastName = rest.join(' ');
|
|
23
|
+
if (!isEmptyField(name) &&
|
|
19
24
|
isValidEmail(inputs.email) &&
|
|
20
|
-
isValidPassword(inputs.password)
|
|
21
|
-
|
|
22
|
-
handleCreateAccount(inputs);
|
|
25
|
+
isValidPassword(inputs.password))
|
|
26
|
+
handleCreateAccount(Object.assign(Object.assign({}, inputs), { firstName, lastName }));
|
|
23
27
|
};
|
|
24
28
|
useEffect(() => {
|
|
25
|
-
|
|
29
|
+
var _a, _b;
|
|
30
|
+
// Prefill the single Name field from any first/last that arrives via props.
|
|
31
|
+
const name = values.name || `${(_a = values.firstName) !== null && _a !== void 0 ? _a : ''} ${(_b = values.lastName) !== null && _b !== void 0 ? _b : ''}`.trim();
|
|
32
|
+
setInputs(Object.assign(Object.assign({}, values), { name }));
|
|
26
33
|
}, [values]);
|
|
27
34
|
return {
|
|
28
35
|
inputs,
|
package/dist/index.d.ts
CHANGED
|
@@ -7,3 +7,5 @@ export { homeAssistantWizardSteps, partnerWizardSteps } from './helpers';
|
|
|
7
7
|
export { billingProxy, dynamicFormProxy, homeCardProxy, partnerContentProxy, partnerDetailsProxy, paymentMethodProxy, recordsInputsProxy, recordsInputsToDBProxy, userDetailsProxy, confirmAddressProxy, addressProxy, } from './proxies';
|
|
8
8
|
import theme from './theme';
|
|
9
9
|
export { theme };
|
|
10
|
+
export { Button, IconButton } from './components/button/Button';
|
|
11
|
+
export type { ButtonProps, IconButtonProps } from './components/button/Button';
|
package/dist/index.js
CHANGED
|
@@ -7,3 +7,4 @@ export { homeAssistantWizardSteps, partnerWizardSteps } from './helpers';
|
|
|
7
7
|
export { billingProxy, dynamicFormProxy, homeCardProxy, partnerContentProxy, partnerDetailsProxy, paymentMethodProxy, recordsInputsProxy, recordsInputsToDBProxy, userDetailsProxy, confirmAddressProxy, addressProxy, } from './proxies';
|
|
8
8
|
import theme from './theme';
|
|
9
9
|
export { theme };
|
|
10
|
+
export { Button, IconButton } from './components/button/Button';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface SignUpFormI {
|
|
2
2
|
confirmPassword?: string;
|
|
3
3
|
email: string;
|
|
4
|
+
name?: string;
|
|
4
5
|
firstName: string;
|
|
5
6
|
lastName: string;
|
|
6
7
|
password?: string;
|
|
@@ -11,6 +12,8 @@ export interface SignUpI {
|
|
|
11
12
|
isPartner?: boolean;
|
|
12
13
|
handleCreateAccount: (form: SignUpFormI) => void;
|
|
13
14
|
handleSignIn: () => void;
|
|
15
|
+
handleGoogleSignIn?: () => void;
|
|
16
|
+
handleAppleSignIn?: () => void;
|
|
14
17
|
showPromoCode?: boolean;
|
|
15
18
|
signupErrorMessage?: string;
|
|
16
19
|
values?: SignUpFormI;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Meta } from '@storybook/react';
|
|
2
|
+
/**
|
|
3
|
+
* Button (atom) — the 3 canonical templates × 2 real sizes, plus shape and
|
|
4
|
+
* the IconButton derivative. Rendered through the real wrapper component.
|
|
5
|
+
* The old 20 theme variants remain untouched (compat shim) during migration.
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: Meta;
|
|
8
|
+
export default _default;
|
|
9
|
+
export declare const Templates: () => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare const Shapes: () => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare const Icons: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { HStack, Text, VStack } from '@chakra-ui/react';
|
|
3
|
+
import { Button, IconButton } from '../../components/button/Button';
|
|
4
|
+
/**
|
|
5
|
+
* Button (atom) — the 3 canonical templates × 2 real sizes, plus shape and
|
|
6
|
+
* the IconButton derivative. Rendered through the real wrapper component.
|
|
7
|
+
* The old 20 theme variants remain untouched (compat shim) during migration.
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
title: 'Foundations/Button (atom)',
|
|
11
|
+
};
|
|
12
|
+
const Row = ({ label, variant, }) => (_jsxs(HStack, { spacing: "4", align: "center", children: [_jsx(Text, { w: "80px", fontSize: "xs", fontFamily: "secondary", color: "text.secondary", children: label }), _jsx(Button, { variant: variant, size: "standard", children: "Continue" }), _jsx(Button, { variant: variant, size: "compact", children: "Continue" }), _jsx(Button, { variant: variant, isDisabled: true, children: "Disabled" })] }));
|
|
13
|
+
export const Templates = () => (_jsxs(VStack, { align: "stretch", spacing: "6", p: "8", bg: "bg.canvas", maxW: "640px", children: [_jsx(Text, { fontFamily: "primary", fontSize: "lg", color: "text.heading", children: "3 templates \u00B7 standard (3.4rem) + compact (2.5rem)" }), _jsx(Row, { label: "filled", variant: "filled" }), _jsx(Row, { label: "outline", variant: "outline" }), _jsx(Row, { label: "ghost", variant: "ghost" })] }));
|
|
14
|
+
export const Shapes = () => (_jsxs(HStack, { spacing: "4", p: "8", bg: "bg.canvas", align: "center", children: [_jsx(Button, { shape: "square", children: "Square" }), _jsx(Button, { shape: "pillLeft", children: "Back" }), _jsx(Button, { shape: "pillRight", children: "Next" }), _jsx(Button, { width: "12rem", children: "Fixed width" })] }));
|
|
15
|
+
export const Icons = () => (_jsxs(HStack, { spacing: "4", p: "8", bg: "bg.canvas", align: "center", children: [_jsx(IconButton, { "aria-label": "Add", variant: "outline", children: "\uFF0B" }), _jsx(IconButton, { "aria-label": "Menu", children: "\u2630" }), _jsx(IconButton, { "aria-label": "Close", size: "compact", children: "\u2715" })] }));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Meta } from '@storybook/react';
|
|
2
|
+
/**
|
|
3
|
+
* Design Tokens — visual reference for the Step 1 semantic token layer.
|
|
4
|
+
*
|
|
5
|
+
* Every value is read from the live Chakra theme via `useTheme()`, so this
|
|
6
|
+
* doc CANNOT drift from `src/theme/index.ts`. Nothing here is hardcoded.
|
|
7
|
+
*/
|
|
8
|
+
declare const _default: Meta;
|
|
9
|
+
export default _default;
|
|
10
|
+
export declare const Colors: () => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare const Radii: () => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare const Shadows: () => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare const Usage: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Center, Code, Divider, Grid, HStack, Text, VStack, useTheme, } from '@chakra-ui/react';
|
|
3
|
+
/**
|
|
4
|
+
* Design Tokens — visual reference for the Step 1 semantic token layer.
|
|
5
|
+
*
|
|
6
|
+
* Every value is read from the live Chakra theme via `useTheme()`, so this
|
|
7
|
+
* doc CANNOT drift from `src/theme/index.ts`. Nothing here is hardcoded.
|
|
8
|
+
*/
|
|
9
|
+
export default {
|
|
10
|
+
title: 'Foundations/Design Tokens',
|
|
11
|
+
};
|
|
12
|
+
// ── helpers ────────────────────────────────────────────────────────
|
|
13
|
+
// Resolve a palette token ref (e.g. 'blue.2', 'overlay') to its raw hex.
|
|
14
|
+
const resolveHex = (theme, token) => {
|
|
15
|
+
const parts = String(token).split('.');
|
|
16
|
+
let cur = theme.colors;
|
|
17
|
+
for (const p of parts)
|
|
18
|
+
cur = cur === null || cur === void 0 ? void 0 : cur[p];
|
|
19
|
+
return typeof cur === 'string' ? cur : token;
|
|
20
|
+
};
|
|
21
|
+
// Convert a radius value ('0.5rem' | '2px' | '9999px') to a px label.
|
|
22
|
+
const toPx = (v) => {
|
|
23
|
+
if (typeof v !== 'string')
|
|
24
|
+
return '';
|
|
25
|
+
if (v.endsWith('rem'))
|
|
26
|
+
return `${parseFloat(v) * 16}px`;
|
|
27
|
+
return v;
|
|
28
|
+
};
|
|
29
|
+
// The 7 semantic groups WE authored, in display order. Explicit list keeps
|
|
30
|
+
// Chakra's built-in semanticTokens (chakra-body-text, …) out of the doc.
|
|
31
|
+
const COLOR_GROUPS = [
|
|
32
|
+
'brand',
|
|
33
|
+
'bg',
|
|
34
|
+
'text',
|
|
35
|
+
'border',
|
|
36
|
+
'link',
|
|
37
|
+
'status',
|
|
38
|
+
'accent',
|
|
39
|
+
];
|
|
40
|
+
const RADII_KEYS = ['none', 'sm', 'base', 'md', 'lg', 'xl', '2xl', 'full'];
|
|
41
|
+
const SHADOW_KEYS = ['focusRing', 'cardHover'];
|
|
42
|
+
// ── shared bits ────────────────────────────────────────────────────
|
|
43
|
+
const SectionTitle = ({ children }) => (_jsx(Text, { fontFamily: "primary", fontSize: "lg", fontWeight: "semibold", color: "text.heading", textTransform: "capitalize", mb: "3", children: children }));
|
|
44
|
+
const ColorSwatch = ({ role, alias, hex, }) => (_jsxs(Box, { borderWidth: "1px", borderColor: "border.subtle", borderRadius: "base", overflow: "hidden", bg: "bg.elevated", children: [_jsx(Box, { bg: role, h: "56px", borderBottomWidth: "1px", borderColor: "blackAlpha.100" }), _jsxs(Box, { p: "2", children: [_jsx(Text, { fontFamily: "secondary", fontSize: "xs", fontWeight: "bold", color: "text.heading", children: role }), _jsxs(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.secondary", children: ["alias: ", alias] }), _jsx(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.muted", textTransform: "uppercase", children: hex })] })] }));
|
|
45
|
+
// ── 1. COLORS ──────────────────────────────────────────────────────
|
|
46
|
+
export const Colors = () => {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const theme = useTheme();
|
|
49
|
+
const sem = (_b = (_a = theme.semanticTokens) === null || _a === void 0 ? void 0 : _a.colors) !== null && _b !== void 0 ? _b : {};
|
|
50
|
+
return (_jsx(VStack, { align: "stretch", spacing: "8", p: "base", maxW: "960px", children: COLOR_GROUPS.map((group) => {
|
|
51
|
+
var _a;
|
|
52
|
+
const roles = (_a = sem[group]) !== null && _a !== void 0 ? _a : {};
|
|
53
|
+
return (_jsxs(Box, { children: [_jsx(SectionTitle, { children: group }), _jsx(Grid, { templateColumns: "repeat(auto-fill, minmax(170px, 1fr))", gap: "4", children: Object.entries(roles).map(([role, val]) => {
|
|
54
|
+
const alias = val === null || val === void 0 ? void 0 : val.default;
|
|
55
|
+
if (!alias)
|
|
56
|
+
return null;
|
|
57
|
+
const fullRole = `${group}.${role}`;
|
|
58
|
+
return (_jsx(ColorSwatch, { role: fullRole, alias: String(alias), hex: resolveHex(theme, alias) }, fullRole));
|
|
59
|
+
}) })] }, group));
|
|
60
|
+
}) }));
|
|
61
|
+
};
|
|
62
|
+
// ── 2. RADII ───────────────────────────────────────────────────────
|
|
63
|
+
export const Radii = () => {
|
|
64
|
+
const theme = useTheme();
|
|
65
|
+
return (_jsxs(Box, { p: "base", maxW: "960px", children: [_jsx(SectionTitle, { children: "Radii" }), _jsx(Grid, { templateColumns: "repeat(auto-fill, minmax(120px, 1fr))", gap: "6", children: RADII_KEYS.map((key) => {
|
|
66
|
+
var _a;
|
|
67
|
+
const value = (_a = theme.radii) === null || _a === void 0 ? void 0 : _a[key];
|
|
68
|
+
if (value === undefined)
|
|
69
|
+
return null;
|
|
70
|
+
return (_jsxs(Center, { flexDirection: "column", children: [_jsx(Box, { boxSize: "80px", bg: "brand.primary", borderRadius: key, mb: "2" }), _jsx(Text, { fontFamily: "secondary", fontSize: "xs", fontWeight: "bold", color: "text.heading", children: key }), _jsxs(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.muted", children: [value, " \u00B7 ", toPx(value)] })] }, key));
|
|
71
|
+
}) })] }));
|
|
72
|
+
};
|
|
73
|
+
// ── 3. SHADOWS ─────────────────────────────────────────────────────
|
|
74
|
+
export const Shadows = () => {
|
|
75
|
+
const theme = useTheme();
|
|
76
|
+
return (_jsxs(Box, { p: "base", maxW: "960px", children: [_jsx(SectionTitle, { children: "Shadows" }), _jsx(HStack, { spacing: "10", flexWrap: "wrap", align: "flex-start", children: SHADOW_KEYS.map((key) => {
|
|
77
|
+
var _a;
|
|
78
|
+
const value = (_a = theme.shadows) === null || _a === void 0 ? void 0 : _a[key];
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return null;
|
|
81
|
+
return (_jsxs(Center, { flexDirection: "column", children: [_jsx(Box, { boxSize: "96px", bg: "bg.elevated", borderRadius: "base", boxShadow: key, mb: "3" }), _jsx(Text, { fontFamily: "secondary", fontSize: "xs", fontWeight: "bold", color: "text.heading", children: key }), _jsx(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.muted", maxW: "140px", textAlign: "center", children: value })] }, key));
|
|
82
|
+
}) })] }));
|
|
83
|
+
};
|
|
84
|
+
// ── 4. USAGE / INTRO ───────────────────────────────────────────────
|
|
85
|
+
export const Usage = () => (_jsxs(Box, { p: "base", maxW: "720px", children: [_jsx(Text, { fontFamily: "primary", fontSize: "3xl", color: "text.heading", mb: "4", children: "Design Tokens" }), _jsxs(Text, { fontFamily: "secondary", fontSize: "md", color: "text.body", mb: "4", children: ["Semantic ", _jsx("b", { children: "roles" }), " that alias our existing palette. Instead of remembering that the brand blue is ", _jsx(Code, { children: "blue.2" }), " or a card background is ", _jsx(Code, { children: "lightBlue.1" }), ", you name the job:"] }), _jsxs(VStack, { align: "stretch", spacing: "1", mb: "5", children: [_jsx(Code, { children: "bg=\"bg.canvas\"" }), _jsx(Code, { children: "bg=\"bg.surface\"" }), _jsx(Code, { children: "color=\"text.heading\"" }), _jsx(Code, { children: "color=\"brand.primary\"" }), _jsx(Code, { children: "borderColor=\"border.default\"" })] }), _jsx(Divider, { mb: "5", borderColor: "border.subtle" }), _jsxs(Text, { fontFamily: "secondary", fontSize: "md", color: "text.body", mb: "3", children: [_jsx("b", { children: "One source of truth." }), " Every role points at one palette token, defined once in ", _jsx(Code, { children: "src/theme/index.ts" }), ". Change", ' ', _jsx(Code, { children: "brand.primary" }), " in that one place and every card, button, link and border that uses the role updates together."] }), _jsxs(Text, { fontFamily: "secondary", fontSize: "md", color: "text.body", children: [_jsx("b", { children: "Additive & opt-in." }), " These tokens are new and change nothing on screen yet \u2014 existing components still use raw palette tokens. Repointing components to these roles happens in a later step (Step 3). Use the roles in ", _jsx("i", { children: "new" }), " work now; nothing breaks."] })] }));
|
package/dist/theme/colors.d.ts
CHANGED
|
@@ -89,6 +89,7 @@ export declare const colors: {
|
|
|
89
89
|
9: string;
|
|
90
90
|
10: string;
|
|
91
91
|
11: string;
|
|
92
|
+
brand: string;
|
|
92
93
|
};
|
|
93
94
|
teal: {
|
|
94
95
|
1: string;
|
|
@@ -113,6 +114,7 @@ export declare const colors: {
|
|
|
113
114
|
error: {
|
|
114
115
|
1: string;
|
|
115
116
|
2: string;
|
|
117
|
+
3: string;
|
|
116
118
|
};
|
|
117
119
|
dayGradient: string;
|
|
118
120
|
morningGradient: string;
|
package/dist/theme/colors.js
CHANGED
|
@@ -89,6 +89,7 @@ export const colors = {
|
|
|
89
89
|
9: '#726E60',
|
|
90
90
|
10: '#5D675B',
|
|
91
91
|
11: '#4C8827',
|
|
92
|
+
brand: '#6ab528', // Homefile logo house mark
|
|
92
93
|
},
|
|
93
94
|
teal: {
|
|
94
95
|
1: '#F2EFF9',
|
|
@@ -113,6 +114,7 @@ export const colors = {
|
|
|
113
114
|
error: {
|
|
114
115
|
1: '#f5ddf4',
|
|
115
116
|
2: '#F544DA',
|
|
117
|
+
3: '#D53F8C', // canonical validation-error color (was rendering via Chakra-default pink.500)
|
|
116
118
|
},
|
|
117
119
|
dayGradient: 'linear(to-b, #cff1fc, #F9FEFF)',
|
|
118
120
|
morningGradient: 'linear(to-b, #d2abf1, #f7dcac)',
|
|
@@ -22,8 +22,90 @@ export declare const Button: {
|
|
|
22
22
|
onboarding: {
|
|
23
23
|
w: string[];
|
|
24
24
|
};
|
|
25
|
+
standard: {
|
|
26
|
+
fontSize: string;
|
|
27
|
+
h: string;
|
|
28
|
+
px: string;
|
|
29
|
+
fontWeight: string;
|
|
30
|
+
};
|
|
31
|
+
compact: {
|
|
32
|
+
fontSize: string;
|
|
33
|
+
h: string;
|
|
34
|
+
px: string;
|
|
35
|
+
fontWeight: string;
|
|
36
|
+
};
|
|
25
37
|
};
|
|
26
38
|
variants: {
|
|
39
|
+
filled: ({ colorScheme: cs }: {
|
|
40
|
+
colorScheme?: string;
|
|
41
|
+
}) => {
|
|
42
|
+
color: string;
|
|
43
|
+
bg: string;
|
|
44
|
+
borderRadius: string;
|
|
45
|
+
boxShadow: string;
|
|
46
|
+
textTransform: string;
|
|
47
|
+
_hover: {
|
|
48
|
+
boxShadow: string;
|
|
49
|
+
};
|
|
50
|
+
_active: {
|
|
51
|
+
bg: string;
|
|
52
|
+
boxShadow: string;
|
|
53
|
+
};
|
|
54
|
+
_focusVisible: {
|
|
55
|
+
boxShadow: string;
|
|
56
|
+
};
|
|
57
|
+
_disabled: {
|
|
58
|
+
filter: string;
|
|
59
|
+
opacity: number;
|
|
60
|
+
pointerEvents: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
outline: ({ colorScheme: cs }: {
|
|
64
|
+
colorScheme?: string;
|
|
65
|
+
}) => {
|
|
66
|
+
color: string;
|
|
67
|
+
bg: string;
|
|
68
|
+
border: string;
|
|
69
|
+
borderColor: string;
|
|
70
|
+
borderRadius: string;
|
|
71
|
+
textTransform: string;
|
|
72
|
+
_hover: {
|
|
73
|
+
bg: string;
|
|
74
|
+
};
|
|
75
|
+
_active: {
|
|
76
|
+
bg: string;
|
|
77
|
+
};
|
|
78
|
+
_focusVisible: {
|
|
79
|
+
boxShadow: string;
|
|
80
|
+
};
|
|
81
|
+
_disabled: {
|
|
82
|
+
filter: string;
|
|
83
|
+
opacity: number;
|
|
84
|
+
pointerEvents: string;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
ghost: ({ colorScheme: cs }: {
|
|
88
|
+
colorScheme?: string;
|
|
89
|
+
}) => {
|
|
90
|
+
color: string;
|
|
91
|
+
bg: string;
|
|
92
|
+
boxShadow: string;
|
|
93
|
+
textTransform: string;
|
|
94
|
+
_hover: {
|
|
95
|
+
color: string;
|
|
96
|
+
bg: string;
|
|
97
|
+
};
|
|
98
|
+
_active: {
|
|
99
|
+
color: string;
|
|
100
|
+
};
|
|
101
|
+
_focusVisible: {
|
|
102
|
+
boxShadow: string;
|
|
103
|
+
};
|
|
104
|
+
_disabled: {
|
|
105
|
+
filter: string;
|
|
106
|
+
pointerEvents: string;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
27
109
|
primary: {
|
|
28
110
|
fontSize: string;
|
|
29
111
|
textTransform: string;
|
|
@@ -22,13 +22,69 @@ export const Button = {
|
|
|
22
22
|
onboarding: {
|
|
23
23
|
w: ['button.full', 'button.md'],
|
|
24
24
|
},
|
|
25
|
+
// ── Canonical sizes (the atom): 2 real heights, type-only. Width is a
|
|
26
|
+
// separate axis set via style props (w / isFullWidth), never baked here.
|
|
27
|
+
standard: { fontSize: 'md', h: 'button.xs', px: '1.4rem', fontWeight: 'medium' }, // 3.4rem
|
|
28
|
+
compact: { fontSize: 'sm', h: 'button.xxs', px: '1rem', fontWeight: 'medium' }, // 2.5rem
|
|
25
29
|
},
|
|
26
30
|
variants: {
|
|
31
|
+
// ── Canonical templates (the atom) ──────────────────────────────
|
|
32
|
+
// 3 templates × colorScheme (brand default; success on demand). Old
|
|
33
|
+
// variants below are deprecated aliases kept for the compat shim —
|
|
34
|
+
// migrate internal usages to these + props, then delete each.
|
|
35
|
+
filled: ({ colorScheme: cs }) => {
|
|
36
|
+
const c = cs === 'success'
|
|
37
|
+
? { base: 'status.success', active: 'green.5' }
|
|
38
|
+
: { base: 'brand.primary', active: 'brand.primaryHover' };
|
|
39
|
+
return {
|
|
40
|
+
color: 'text.inverse',
|
|
41
|
+
bg: c.base,
|
|
42
|
+
borderRadius: 'none',
|
|
43
|
+
boxShadow: 'base',
|
|
44
|
+
textTransform: 'none',
|
|
45
|
+
_hover: { boxShadow: 'xl' },
|
|
46
|
+
_active: { bg: c.active, boxShadow: 'none' },
|
|
47
|
+
_focusVisible: { boxShadow: 'focusRing' },
|
|
48
|
+
_disabled: { filter: 'grayscale(100%)', opacity: 1, pointerEvents: 'none' },
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
outline: ({ colorScheme: cs }) => {
|
|
52
|
+
const c = cs === 'success'
|
|
53
|
+
? { edge: 'status.success', tint: 'lightGreen.3', press: 'lightGreen.4' }
|
|
54
|
+
: { edge: 'brand.primary', tint: 'lightBlue.3', press: 'lightBlue.5' };
|
|
55
|
+
return {
|
|
56
|
+
color: c.edge,
|
|
57
|
+
bg: 'transparent',
|
|
58
|
+
border: '1px solid',
|
|
59
|
+
borderColor: c.edge,
|
|
60
|
+
borderRadius: 'none',
|
|
61
|
+
textTransform: 'none',
|
|
62
|
+
_hover: { bg: c.tint },
|
|
63
|
+
_active: { bg: c.press },
|
|
64
|
+
_focusVisible: { boxShadow: 'focusRing' },
|
|
65
|
+
_disabled: { filter: 'grayscale(100%)', opacity: 1, pointerEvents: 'none' },
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
ghost: ({ colorScheme: cs }) => {
|
|
69
|
+
const c = cs === 'success'
|
|
70
|
+
? { text: 'status.success', hover: 'green.5' }
|
|
71
|
+
: { text: 'brand.primary', hover: 'brand.primaryHover' };
|
|
72
|
+
return {
|
|
73
|
+
color: c.text,
|
|
74
|
+
bg: 'transparent',
|
|
75
|
+
boxShadow: 'none',
|
|
76
|
+
textTransform: 'none',
|
|
77
|
+
_hover: { color: c.hover, bg: 'transparent' },
|
|
78
|
+
_active: { color: c.hover },
|
|
79
|
+
_focusVisible: { boxShadow: 'focusRing' },
|
|
80
|
+
_disabled: { filter: 'grayscale(100%)', pointerEvents: 'none' },
|
|
81
|
+
};
|
|
82
|
+
},
|
|
27
83
|
primary: {
|
|
28
84
|
fontSize: 'md',
|
|
29
|
-
textTransform: '
|
|
85
|
+
textTransform: 'none',
|
|
30
86
|
h: 'button.xs',
|
|
31
|
-
fontWeight: '
|
|
87
|
+
fontWeight: 'medium',
|
|
32
88
|
borderRadius: 'none',
|
|
33
89
|
boxShadow: 'base',
|
|
34
90
|
color: 'neutral.white',
|
|
@@ -49,10 +105,10 @@ export const Button = {
|
|
|
49
105
|
},
|
|
50
106
|
secondary: {
|
|
51
107
|
fontSize: 'md',
|
|
52
|
-
textTransform: '
|
|
108
|
+
textTransform: 'none',
|
|
53
109
|
h: 'button.xs',
|
|
54
110
|
px: '1rem',
|
|
55
|
-
fontWeight: '
|
|
111
|
+
fontWeight: 'medium',
|
|
56
112
|
borderRadius: 'none',
|
|
57
113
|
boxShadow: 'base',
|
|
58
114
|
backgroundColor: 'transparent',
|
|
@@ -66,7 +122,7 @@ export const Button = {
|
|
|
66
122
|
boxShadow: 'none',
|
|
67
123
|
},
|
|
68
124
|
_hover: {
|
|
69
|
-
backgroundColor: '
|
|
125
|
+
backgroundColor: 'lightBlue.3',
|
|
70
126
|
boxShadow: 'lg',
|
|
71
127
|
},
|
|
72
128
|
_disabled: {
|
|
@@ -236,7 +292,7 @@ export const Button = {
|
|
|
236
292
|
},
|
|
237
293
|
rightRounded: {
|
|
238
294
|
fontSize: 'md',
|
|
239
|
-
textTransform: '
|
|
295
|
+
textTransform: 'none',
|
|
240
296
|
h: 'button.xs',
|
|
241
297
|
pr: '3rem',
|
|
242
298
|
fontWeight: 'semibold',
|
|
@@ -259,7 +315,7 @@ export const Button = {
|
|
|
259
315
|
},
|
|
260
316
|
menu: {
|
|
261
317
|
fontSize: 'md',
|
|
262
|
-
textTransform: '
|
|
318
|
+
textTransform: 'none',
|
|
263
319
|
h: 'button.xs',
|
|
264
320
|
fontWeight: 'regular',
|
|
265
321
|
borderRadius: 'none',
|
|
@@ -285,7 +341,7 @@ export const Button = {
|
|
|
285
341
|
maxW: 'fit-content',
|
|
286
342
|
px: '1rem',
|
|
287
343
|
fontSize: 'md',
|
|
288
|
-
textTransform: '
|
|
344
|
+
textTransform: 'none',
|
|
289
345
|
h: 'button.xxs',
|
|
290
346
|
fontWeight: 'semibold',
|
|
291
347
|
borderRadius: 'none',
|
|
@@ -25,7 +25,7 @@ export const Input = {
|
|
|
25
25
|
borderColor: 'lightBlue.6',
|
|
26
26
|
borderRadius: 'sm',
|
|
27
27
|
_placeholder: {
|
|
28
|
-
color: '
|
|
28
|
+
color: 'text.secondary', // #818181 — single source; consistent across all inputs
|
|
29
29
|
},
|
|
30
30
|
_hover: {
|
|
31
31
|
borderColor: 'blue.1',
|
|
@@ -44,7 +44,7 @@ export const Input = {
|
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
46
|
_invalid: {
|
|
47
|
-
borderColor: '
|
|
47
|
+
borderColor: 'status.error',
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
},
|
|
@@ -72,7 +72,7 @@ export const Input = {
|
|
|
72
72
|
},
|
|
73
73
|
},
|
|
74
74
|
_invalid: {
|
|
75
|
-
borderColor: '
|
|
75
|
+
borderColor: 'status.error',
|
|
76
76
|
},
|
|
77
77
|
},
|
|
78
78
|
},
|
package/dist/theme/index.js
CHANGED
|
@@ -37,6 +37,58 @@ const theme = extendTheme({
|
|
|
37
37
|
Textarea,
|
|
38
38
|
},
|
|
39
39
|
colors,
|
|
40
|
+
// Semantic token layer: purpose-named roles that ALIAS existing palette
|
|
41
|
+
// tokens (no new colors). Light-only ({ default }); dark can bolt on later.
|
|
42
|
+
// Consume as color props, e.g. bg="bg.canvas", color="text.heading".
|
|
43
|
+
semanticTokens: {
|
|
44
|
+
colors: {
|
|
45
|
+
brand: {
|
|
46
|
+
primary: { default: 'blue.2' }, // #2488C6
|
|
47
|
+
primaryHover: { default: 'blue.3' }, // #1D7AB4
|
|
48
|
+
primaryActive: { default: 'blue.8' }, // #1E6388
|
|
49
|
+
logo: { default: 'green.brand' }, // #6ab528 — logo house mark
|
|
50
|
+
},
|
|
51
|
+
bg: {
|
|
52
|
+
canvas: { default: 'lightBlue.4' }, // #DCE8EC — app body (matches global)
|
|
53
|
+
surface: { default: 'lightBlue.1' }, // #F4F7F8 — cards / tiles
|
|
54
|
+
elevated: { default: 'neutral.white' }, // #FFFFFF
|
|
55
|
+
subtle: { default: 'lightBlue.2' }, // #E9EDEF
|
|
56
|
+
hover: { default: 'lightGreen.1' }, // #F0F8F0 — row hover
|
|
57
|
+
overlay: { default: 'overlay' }, // #00000040 — modal scrim
|
|
58
|
+
},
|
|
59
|
+
text: {
|
|
60
|
+
heading: { default: 'gray.4' }, // #37323E
|
|
61
|
+
body: { default: 'gray.3' }, // #464646
|
|
62
|
+
secondary: { default: 'gray.2' }, // #818181
|
|
63
|
+
muted: { default: 'gray.1' }, // #ADB6BA
|
|
64
|
+
inverse: { default: 'neutral.white' }, // on dark/brand fills
|
|
65
|
+
},
|
|
66
|
+
border: {
|
|
67
|
+
default: { default: 'lightBlue.6' }, // #BCD1D8
|
|
68
|
+
subtle: { default: 'lightGray.1' }, // #E2EBEF
|
|
69
|
+
focus: { default: 'blue.2' }, // #2488C6
|
|
70
|
+
},
|
|
71
|
+
link: {
|
|
72
|
+
default: { default: 'blue.2' }, // #2488C6 — matches global `a`
|
|
73
|
+
hover: { default: 'blue.3' }, // #1D7AB4
|
|
74
|
+
},
|
|
75
|
+
status: {
|
|
76
|
+
success: { default: 'green.1' }, // #519F0E
|
|
77
|
+
successBg: { default: 'lightGreen.1' }, // #F0F8F0
|
|
78
|
+
error: { default: 'error.3' }, // #D53F8C — deliberately magenta, NOT red (red is used elsewhere in-app)
|
|
79
|
+
errorBg: { default: 'error.1' }, // #f5ddf4
|
|
80
|
+
warning: { default: 'orange.2' }, // #EA6808
|
|
81
|
+
warningBg: { default: 'orange.1' }, // #FDEDE2
|
|
82
|
+
info: { default: 'blue.2' }, // #2488C6
|
|
83
|
+
infoBg: { default: 'lightBlue.3' }, // #DFEFF8
|
|
84
|
+
caution: { default: 'yellow.1' }, // #DEB841
|
|
85
|
+
},
|
|
86
|
+
accent: {
|
|
87
|
+
homi: { default: 'teal.3' }, // #13C0B7 — Homi voice / special moments
|
|
88
|
+
violet: { default: 'violet.1' }, // #6041B7 — insight/accent
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
40
92
|
fonts: {
|
|
41
93
|
primary: 'Poppins',
|
|
42
94
|
secondary: 'Assistant',
|
|
@@ -134,5 +186,23 @@ const theme = extendTheme({
|
|
|
134
186
|
wMd: '22rem',
|
|
135
187
|
},
|
|
136
188
|
},
|
|
189
|
+
// Corner-radius scale — declared at Chakra defaults so existing
|
|
190
|
+
// borderRadius="sm|base|md|lg|xl" render identically; makes the scale ours (SSOT).
|
|
191
|
+
radii: {
|
|
192
|
+
none: '0',
|
|
193
|
+
sm: '0.125rem', // 2px
|
|
194
|
+
base: '0.25rem', // 4px
|
|
195
|
+
md: '0.375rem', // 6px
|
|
196
|
+
lg: '0.5rem', // 8px
|
|
197
|
+
xl: '0.75rem', // 12px
|
|
198
|
+
'2xl': '1rem', // 16px
|
|
199
|
+
full: '9999px',
|
|
200
|
+
},
|
|
201
|
+
// NEW, non-colliding shadow keys only — does NOT redefine Chakra's numeric
|
|
202
|
+
// keys, so existing boxShadow="base|lg|2xl" keep their current values.
|
|
203
|
+
shadows: {
|
|
204
|
+
focusRing: '0 0 0 2px #2488C6', // = blue.2 (v2 shadow strings can't ref color tokens)
|
|
205
|
+
cardHover: '0 4px 12px rgba(36,136,198,0.15)', // brand-tinted
|
|
206
|
+
},
|
|
137
207
|
});
|
|
138
208
|
export default theme;
|
|
@@ -22,7 +22,7 @@ export const isValidEmail = (email = '') => {
|
|
|
22
22
|
return true;
|
|
23
23
|
};
|
|
24
24
|
export const isValidPassword = (password = '') => {
|
|
25
|
-
return password.trim().length >=
|
|
25
|
+
return password.trim().length >= 8;
|
|
26
26
|
};
|
|
27
27
|
export const passwordMatch = (password = '', confirmPassword = '') => {
|
|
28
28
|
return password.trim() === confirmPassword.trim();
|