property-practice-ui 0.0.1
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/.prettierignore +3 -0
- package/.prettierrc +4 -0
- package/.storybook/main.ts +30 -0
- package/.storybook/preview.ts +32 -0
- package/.storybook/vitest.setup.ts +6 -0
- package/CHANGELOG.md +7 -0
- package/dist/index.js +10148 -0
- package/dist/index.mjs +10148 -0
- package/eslint.config.mjs +4 -0
- package/package.json +63 -0
- package/postcss.config.js +6 -0
- package/src/atoms/ArrowButton/ArrowButton.stories.tsx +52 -0
- package/src/atoms/ArrowButton/ArrowButton.tsx +71 -0
- package/src/atoms/Button/Button.stories.tsx +45 -0
- package/src/atoms/Button/Button.tsx +91 -0
- package/src/atoms/CheckboxItem/CheckboxItem.tsx +49 -0
- package/src/atoms/Description/Description.tsx +43 -0
- package/src/atoms/ExtendedButton/ExtendedButton.stories.tsx +61 -0
- package/src/atoms/ExtendedButton/ExtendedButton.tsx +40 -0
- package/src/atoms/FeatureItem/FeatureItem.stories.tsx +25 -0
- package/src/atoms/FeatureItem/FeatureItem.tsx +90 -0
- package/src/atoms/FormContainer/FormContainer.tsx +34 -0
- package/src/atoms/Header/Header.stories.tsx +22 -0
- package/src/atoms/Header/Header.tsx +53 -0
- package/src/atoms/Input/Input.stories.tsx +23 -0
- package/src/atoms/Input/Input.tsx +28 -0
- package/src/atoms/Label/Label.stories.tsx +28 -0
- package/src/atoms/Label/Label.tsx +53 -0
- package/src/atoms/Loader/Loader.tsx +49 -0
- package/src/atoms/Pill/Pill.stories.tsx +30 -0
- package/src/atoms/Pill/Pill.tsx +82 -0
- package/src/atoms/RadioItem/RadioItem.stories.tsx +25 -0
- package/src/atoms/RadioItem/RadioItem.tsx +54 -0
- package/src/atoms/SecondaryInput/SecondaryInput.stories.tsx +30 -0
- package/src/atoms/SecondaryInput/SecondaryInput.tsx +125 -0
- package/src/atoms/SocialButton/SocialButton.stories.tsx +79 -0
- package/src/atoms/SocialButton/SocialButton.tsx +90 -0
- package/src/atoms/TermsCheckbox/TermsCheckbox.stories.tsx +62 -0
- package/src/atoms/TermsCheckbox/TermsCheckbox.tsx +150 -0
- package/src/atoms/Text/Text.stories.tsx +32 -0
- package/src/atoms/Text/Text.tsx +49 -0
- package/src/atoms/TextButton/TextButton.stories.tsx +77 -0
- package/src/atoms/TextButton/TextButton.tsx +78 -0
- package/src/atoms/Textarea/Textarea.tsx +36 -0
- package/src/atoms/ToggleButton/ToggleButton.stories.tsx +32 -0
- package/src/atoms/ToggleButton/ToggleButton.tsx +106 -0
- package/src/atoms/index.ts +20 -0
- package/src/components/DynamicInput.tsx +54 -0
- package/src/components/FileUpload.tsx +123 -0
- package/src/components/Filter/Filter.tsx +33 -0
- package/src/components/ModeSwitch.tsx +66 -0
- package/src/components/NavMenu.tsx +83 -0
- package/src/components/SearchBar/Search.stories.tsx +25 -0
- package/src/components/SearchBar/Search.tsx +40 -0
- package/src/components/SortBy/SortBy.stories.tsx +27 -0
- package/src/components/SortBy/SortBy.tsx +45 -0
- package/src/components/Spinner.tsx +30 -0
- package/src/components/Table/Table.stories.tsx +56 -0
- package/src/components/Table/Table.tsx +25 -0
- package/src/components/TableInner/TableInner.tsx +27 -0
- package/src/components/TableInner/tableInner.stories.tsx +21 -0
- package/src/components/TableList.tsx +195 -0
- package/src/components/TableRow/TableRow.stories.tsx +55 -0
- package/src/components/TableRow/TableRow.tsx +343 -0
- package/src/components/Tabs/Tabs.stories.tsx +42 -0
- package/src/components/Tabs/Tabs.tsx +56 -0
- package/src/components/Toast.tsx +192 -0
- package/src/components/TopMenu.tsx +62 -0
- package/src/index.ts +20 -0
- package/src/molecules/Accordion/Accordion.stories.tsx +54 -0
- package/src/molecules/Accordion/Accordion.tsx +45 -0
- package/src/molecules/AccordionContent/AccordionContent.tsx +16 -0
- package/src/molecules/AccordionHeader/AccordionHeader.stories.tsx +31 -0
- package/src/molecules/AccordionHeader/AccordionHeader.tsx +67 -0
- package/src/molecules/Address/Address.stories.tsx +116 -0
- package/src/molecules/Address/Address.tsx +136 -0
- package/src/molecules/CTAContainer/CTAContainer.stories.tsx +67 -0
- package/src/molecules/CTAContainer/CTAContainer.tsx +98 -0
- package/src/molecules/Checkbox/Checkbox.tsx +60 -0
- package/src/molecules/ContentCard/ContentCard.stories.tsx +31 -0
- package/src/molecules/ContentCard/ContentCard.tsx +80 -0
- package/src/molecules/DocumentAccordionHeader/DocumentAccordionHeader.tsx +50 -0
- package/src/molecules/DocumentAccordionRow/DocumentAccordionRow.tsx +66 -0
- package/src/molecules/Dropdown/Dropdown.stories.tsx +39 -0
- package/src/molecules/Dropdown/Dropdown.tsx +89 -0
- package/src/molecules/EmptyState/EmptyState.stories.tsx +26 -0
- package/src/molecules/EmptyState/EmptyState.tsx +49 -0
- package/src/molecules/FAQAccordion/FAQAccordion.stories.tsx +63 -0
- package/src/molecules/FAQAccordion/FAQAccordion.tsx +98 -0
- package/src/molecules/FeatureContainer/FeatureContainer.stories.tsx +40 -0
- package/src/molecules/FeatureContainer/FeatureContainer.tsx +59 -0
- package/src/molecules/FileButton/FileButton.tsx +54 -0
- package/src/molecules/Input/Input.stories.tsx +30 -0
- package/src/molecules/Input/Input.tsx +31 -0
- package/src/molecules/InputContainer/InputContainer.tsx +60 -0
- package/src/molecules/JutaBrand/JutaBrand.stories.tsx +43 -0
- package/src/molecules/JutaBrand/JutaBrand.tsx +74 -0
- package/src/molecules/Modal/Modal.tsx +71 -0
- package/src/molecules/OverviewRowItem/OverviewRowItem.stories.tsx +23 -0
- package/src/molecules/OverviewRowItem/OverviewRowItem.tsx +39 -0
- package/src/molecules/PDFPreviewer/PDFPreviewer.tsx +42 -0
- package/src/molecules/PageLayout/PageLayout.tsx +60 -0
- package/src/molecules/ProductInfo/ProductInfo.stories.tsx +50 -0
- package/src/molecules/ProductInfo/ProductInfo.tsx +90 -0
- package/src/molecules/RadioGroup/RadioGroup.stories.tsx +47 -0
- package/src/molecules/RadioGroup/RadioGroup.tsx +55 -0
- package/src/molecules/RatesChart/RatesChart.stories.tsx +61 -0
- package/src/molecules/RatesChart/RatesChart.tsx +185 -0
- package/src/molecules/SideNav/SideNav.stories.tsx +64 -0
- package/src/molecules/SideNav/SideNav.tsx +140 -0
- package/src/molecules/SidePanel/SidePanel.stories.tsx +87 -0
- package/src/molecules/SidePanel/SidePanel.tsx +138 -0
- package/src/molecules/StepperHeaderTab/StepperHeaderTab.stories.tsx +23 -0
- package/src/molecules/StepperHeaderTab/StepperHeaderTab.tsx +43 -0
- package/src/molecules/Textarea/Textarea.tsx +29 -0
- package/src/molecules/index.ts +23 -0
- package/src/organism/ContactForm/ContactForm.stories.tsx +15 -0
- package/src/organism/ContactForm/ContactForm.tsx +54 -0
- package/src/organism/DocumentListAccordion/DocumentListAccordion.tsx +96 -0
- package/src/organism/FeatureCarousel/FeatureCarousel.stories.tsx +81 -0
- package/src/organism/FeatureCarousel/FeatureCarousel.tsx +162 -0
- package/src/organism/Footer/Footer.stories.tsx +77 -0
- package/src/organism/Footer/Footer.tsx +231 -0
- package/src/organism/Header/Header.stories.tsx +51 -0
- package/src/organism/Header/Header.tsx +76 -0
- package/src/organism/OverviewList/OverviewList.stories.tsx +43 -0
- package/src/organism/OverviewList/OverviewList.tsx +56 -0
- package/src/organism/ToastProvider/ToastProvider.tsx +40 -0
- package/src/organism/VersionLabel/VersionLabel.tsx +9 -0
- package/src/organism/index.ts +9 -0
- package/src/styles/tailwind.css +8 -0
- package/src/templates/AboutUs/AboutUs.stories.tsx +60 -0
- package/src/templates/AboutUs/AboutUs.tsx +97 -0
- package/src/templates/Contact/Contact.stories.tsx +62 -0
- package/src/templates/Contact/Contact.tsx +125 -0
- package/src/templates/FAQ/FAQ.stories.tsx +82 -0
- package/src/templates/FAQ/FAQ.tsx +91 -0
- package/src/templates/Features/Features.stories.tsx +94 -0
- package/src/templates/Features/Features.tsx +79 -0
- package/src/templates/Hero/Hero.stories.tsx +105 -0
- package/src/templates/Hero/Hero.tsx +139 -0
- package/src/templates/OtherProducts/OtherProducts.stories.tsx +77 -0
- package/src/templates/OtherProducts/OtherProducts.tsx +86 -0
- package/src/templates/index.ts +7 -0
- package/src/tokens/animations.ts +11 -0
- package/src/tokens/breakpoints.ts +7 -0
- package/src/tokens/colors.stories.tsx +77 -0
- package/src/tokens/colors.ts +59 -0
- package/src/tokens/radii.ts +6 -0
- package/src/tokens/sizes.ts +8 -0
- package/src/tokens/spaces.ts +21 -0
- package/src/types.ts +20 -0
- package/stories/Button.stories.ts +54 -0
- package/stories/Button.tsx +41 -0
- package/stories/Configure.mdx +364 -0
- package/stories/Header.stories.ts +34 -0
- package/stories/Header.tsx +71 -0
- package/stories/Page.stories.ts +33 -0
- package/stories/Page.tsx +91 -0
- package/stories/TopMenu.stories.tsx +51 -0
- package/stories/assets/accessibility.png +0 -0
- package/stories/assets/accessibility.svg +1 -0
- package/stories/assets/addon-library.png +0 -0
- package/stories/assets/assets.png +0 -0
- package/stories/assets/avif-test-image.avif +0 -0
- package/stories/assets/context.png +0 -0
- package/stories/assets/discord.svg +1 -0
- package/stories/assets/docs.png +0 -0
- package/stories/assets/figma-plugin.png +0 -0
- package/stories/assets/github.svg +1 -0
- package/stories/assets/share.png +0 -0
- package/stories/assets/styling.png +0 -0
- package/stories/assets/testing.png +0 -0
- package/stories/assets/theming.png +0 -0
- package/stories/assets/tutorials.svg +1 -0
- package/stories/assets/youtube.svg +1 -0
- package/stories/button.css +30 -0
- package/stories/header.css +32 -0
- package/stories/page.css +68 -0
- package/tailwind.config.js +34 -0
- package/tsconfig.json +8 -0
- package/types/index.ts +5 -0
- package/types/inputAttributes.ts +16 -0
- package/types/menuItem.ts +17 -0
- package/types/orderType.ts +2 -0
- package/types/tableListItem.ts +7 -0
- package/types/toast.ts +1 -0
- package/vitest.config.ts +37 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { HiMinus, HiPlus } from 'react-icons/hi2';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { Description, Header } from '../../atoms';
|
|
5
|
+
import { colors } from '../../tokens/colors';
|
|
6
|
+
|
|
7
|
+
type TextColor = keyof typeof colors.text;
|
|
8
|
+
|
|
9
|
+
const AccordionContainer = styled.div`
|
|
10
|
+
border-bottom: 1px solid ${colors.text.light || '#d1d5db'};
|
|
11
|
+
padding: 1.5rem 0;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const AccordionHeader = styled.button`
|
|
15
|
+
width: 100%;
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: 1rem;
|
|
19
|
+
background: none;
|
|
20
|
+
border: none;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
text-align: left;
|
|
23
|
+
padding: 0;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const NumberBadge = styled.span<{ numberColor: TextColor }>`
|
|
27
|
+
font-size: 1rem;
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
color: ${props => colors.text[props.numberColor]};
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const HeaderWrapper = styled.div`
|
|
34
|
+
flex-grow: 1;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const IconWrapper = styled.div<{ iconColor: TextColor }>`
|
|
38
|
+
color: ${props => colors.text[props.iconColor]};
|
|
39
|
+
flex-shrink: 0;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const AccordionContent = styled.div<{ $isOpen: boolean }>`
|
|
46
|
+
max-height: ${props => props.$isOpen ? '1000px' : '0'};
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
transition: max-height 0.3s ease-in-out;
|
|
49
|
+
padding-left: 2.5rem;
|
|
50
|
+
margin-top: ${props => props.$isOpen ? '1rem' : '0'};
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
interface AccordionProps {
|
|
54
|
+
number: string;
|
|
55
|
+
title: string;
|
|
56
|
+
content: string;
|
|
57
|
+
defaultOpen?: boolean;
|
|
58
|
+
headerVariant?: 'h1' | 'h2' | 'h3';
|
|
59
|
+
numberColor?: TextColor;
|
|
60
|
+
titleColor?: TextColor;
|
|
61
|
+
iconColor?: TextColor;
|
|
62
|
+
contentVariant?: 'primary' | 'secondary' | 'subtle' | 'error';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const FAQAccordion = ({
|
|
66
|
+
number,
|
|
67
|
+
title,
|
|
68
|
+
content,
|
|
69
|
+
defaultOpen = false,
|
|
70
|
+
headerVariant = 'h3',
|
|
71
|
+
numberColor = 'brand',
|
|
72
|
+
titleColor = 'brand',
|
|
73
|
+
iconColor = 'brand',
|
|
74
|
+
contentVariant = 'primary',
|
|
75
|
+
}: AccordionProps) => {
|
|
76
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<AccordionContainer>
|
|
80
|
+
<AccordionHeader onClick={() => setIsOpen(!isOpen)}>
|
|
81
|
+
<NumberBadge numberColor={numberColor}>{number}</NumberBadge>
|
|
82
|
+
<HeaderWrapper>
|
|
83
|
+
<Header variant={headerVariant} color={titleColor}>
|
|
84
|
+
{title}
|
|
85
|
+
</Header>
|
|
86
|
+
</HeaderWrapper>
|
|
87
|
+
<IconWrapper iconColor={iconColor}>
|
|
88
|
+
{isOpen ? <HiMinus size={24} /> : <HiPlus size={24} />}
|
|
89
|
+
</IconWrapper>
|
|
90
|
+
</AccordionHeader>
|
|
91
|
+
<AccordionContent $isOpen={isOpen}>
|
|
92
|
+
<Description variant={contentVariant}>
|
|
93
|
+
{content}
|
|
94
|
+
</Description>
|
|
95
|
+
</AccordionContent>
|
|
96
|
+
</AccordionContainer>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import { GiMolecule } from "react-icons/gi";
|
|
4
|
+
import { FeatureContainer as Component } from './FeatureContainer';
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Molecules/FeatureContainer',
|
|
8
|
+
component: Component,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
const featureItems = [
|
|
14
|
+
{
|
|
15
|
+
thumbnail: <GiMolecule size={24} color="white" />,
|
|
16
|
+
title: 'End-to-End Process Issuing',
|
|
17
|
+
description: 'Litigator manages the full issuing process for you.',
|
|
18
|
+
thumbnailBgColor: '#0d9488',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
thumbnail: <GiMolecule size={24} color="white" />,
|
|
22
|
+
title: 'Automated Workflow',
|
|
23
|
+
description: 'Streamline your processes with intelligent automation.',
|
|
24
|
+
thumbnailBgColor: '#0d9488',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
thumbnail: <GiMolecule size={24} color="white" />,
|
|
28
|
+
title: 'Real-time Updates',
|
|
29
|
+
description: 'Stay informed with instant notifications and tracking.',
|
|
30
|
+
thumbnailBgColor: '#0d9488',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const FeatureContainer: StoryObj<
|
|
35
|
+
Pick<ComponentProps<typeof Component>, 'items'>
|
|
36
|
+
> = {
|
|
37
|
+
args: {
|
|
38
|
+
items: featureItems,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { FeatureItem } from '../../atoms';
|
|
3
|
+
import { colors } from '../../tokens/colors';
|
|
4
|
+
|
|
5
|
+
type TextColor = keyof typeof colors.text;
|
|
6
|
+
type BackgroundColor = keyof typeof colors.background;
|
|
7
|
+
|
|
8
|
+
const Container = styled.div`
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
const FeatureWrapper = styled.div`
|
|
14
|
+
padding: 1rem 0;
|
|
15
|
+
border-bottom: 1px solid #e5e7eb;
|
|
16
|
+
|
|
17
|
+
&:last-child {
|
|
18
|
+
border-bottom: none;
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
interface FeatureItemData {
|
|
23
|
+
thumbnail: React.ReactNode;
|
|
24
|
+
title: string;
|
|
25
|
+
description: string;
|
|
26
|
+
thumbnailSize?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface FeatureContainerProps {
|
|
30
|
+
items: FeatureItemData[];
|
|
31
|
+
thumbnailBgColor?: BackgroundColor;
|
|
32
|
+
titleColor?: TextColor;
|
|
33
|
+
descriptionVariant?: 'primary' | 'secondary' | 'subtle' | 'error';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const FeatureContainer = ({
|
|
37
|
+
items,
|
|
38
|
+
thumbnailBgColor = 'brand',
|
|
39
|
+
titleColor = 'primary',
|
|
40
|
+
descriptionVariant = 'subtle',
|
|
41
|
+
}: FeatureContainerProps) => {
|
|
42
|
+
return (
|
|
43
|
+
<Container>
|
|
44
|
+
{items.map((item, index) => (
|
|
45
|
+
<FeatureWrapper key={index}>
|
|
46
|
+
<FeatureItem
|
|
47
|
+
thumbnail={item.thumbnail}
|
|
48
|
+
title={item.title}
|
|
49
|
+
description={item.description}
|
|
50
|
+
thumbnailSize={item.thumbnailSize}
|
|
51
|
+
thumbnailBgColor={thumbnailBgColor}
|
|
52
|
+
titleColor={titleColor}
|
|
53
|
+
descriptionVariant={descriptionVariant}
|
|
54
|
+
/>
|
|
55
|
+
</FeatureWrapper>
|
|
56
|
+
))}
|
|
57
|
+
</Container>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ComponentProps, useRef } from 'react';
|
|
2
|
+
import { FaPlus } from 'react-icons/fa6';
|
|
3
|
+
import { Button } from '../../atoms';
|
|
4
|
+
|
|
5
|
+
const acceptTypes = ['.docx', '.doc', '.pdf', 'image/*'] as const;
|
|
6
|
+
|
|
7
|
+
type AcceptType = (typeof acceptTypes)[number];
|
|
8
|
+
|
|
9
|
+
interface FileButtonProps
|
|
10
|
+
extends Omit<ComponentProps<typeof Button>, 'onClick' | 'type'> {
|
|
11
|
+
onFileSelect?: (file: File) => void;
|
|
12
|
+
accept?: AcceptType[];
|
|
13
|
+
multiple?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const FileButton = ({
|
|
17
|
+
onFileSelect,
|
|
18
|
+
accept = ['.pdf'],
|
|
19
|
+
multiple = false,
|
|
20
|
+
...rest
|
|
21
|
+
}: FileButtonProps) => {
|
|
22
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
23
|
+
|
|
24
|
+
const handleClick = () => {
|
|
25
|
+
if (fileInputRef.current) {
|
|
26
|
+
fileInputRef.current.click();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
31
|
+
const files = event.target.files;
|
|
32
|
+
if (files && files.length > 0 && onFileSelect) {
|
|
33
|
+
Array.from(files).forEach((file) => onFileSelect(file));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Button
|
|
40
|
+
{...rest}
|
|
41
|
+
icon={<FaPlus fontSize="10px" />}
|
|
42
|
+
onClick={handleClick}
|
|
43
|
+
/>
|
|
44
|
+
<input
|
|
45
|
+
type="file"
|
|
46
|
+
ref={fileInputRef}
|
|
47
|
+
style={{ display: 'none' }}
|
|
48
|
+
onChange={handleFileChange}
|
|
49
|
+
accept={accept?.map((item) => item).join(',')}
|
|
50
|
+
multiple={multiple}
|
|
51
|
+
/>
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import { Input as Component } from './Input';
|
|
4
|
+
|
|
5
|
+
const meta: Meta = {
|
|
6
|
+
title: 'Molecules/Input',
|
|
7
|
+
component: Component,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
export const Input: StoryObj<
|
|
13
|
+
Pick<
|
|
14
|
+
ComponentProps<typeof Component>,
|
|
15
|
+
'label' | 'name' | 'placeholder' | 'type' | 'error'
|
|
16
|
+
>
|
|
17
|
+
> = {
|
|
18
|
+
args: {
|
|
19
|
+
label: 'First Name',
|
|
20
|
+
placeholder: 'Full Name',
|
|
21
|
+
type: 'text',
|
|
22
|
+
error: 'default',
|
|
23
|
+
},
|
|
24
|
+
argTypes: {
|
|
25
|
+
label: { control: 'text' },
|
|
26
|
+
placeholder: { control: 'text' },
|
|
27
|
+
type: { control: 'select', options: Component.types },
|
|
28
|
+
error: { control: 'text' },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ComponentProps, useMemo } from 'react';
|
|
2
|
+
import { Input as InputBase } from '../../atoms/Input/Input';
|
|
3
|
+
import { FloatingLabel } from '../../atoms/Label/Label';
|
|
4
|
+
import { InputContainer } from '../InputContainer/InputContainer';
|
|
5
|
+
|
|
6
|
+
interface InputProps extends ComponentProps<typeof InputBase> {
|
|
7
|
+
label?: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Input = ({ label, error, ...rest }: InputProps) => {
|
|
12
|
+
const isInvalid = useMemo(
|
|
13
|
+
() => typeof error === 'string' && error.length > 0,
|
|
14
|
+
[error],
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<InputContainer error={error}>
|
|
19
|
+
{typeof label === 'string' && (
|
|
20
|
+
<FloatingLabel
|
|
21
|
+
color={isInvalid ? 'error' : 'subtle'}
|
|
22
|
+
value={label}
|
|
23
|
+
fontWeight="600"
|
|
24
|
+
/>
|
|
25
|
+
)}
|
|
26
|
+
<InputBase {...rest} />
|
|
27
|
+
</InputContainer>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Input.types = InputBase.types;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { Label } from '../../atoms';
|
|
4
|
+
import { colors } from '../../tokens/colors';
|
|
5
|
+
import { radii } from '../../tokens/radii';
|
|
6
|
+
import { sizes } from '../../tokens/sizes';
|
|
7
|
+
import { spaces } from '../../tokens/spaces';
|
|
8
|
+
|
|
9
|
+
const variants = ['outline', 'none'] as const;
|
|
10
|
+
interface InputContainerProps {
|
|
11
|
+
error?: string;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
variant?: (typeof variants)[number];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Container = styled.div`
|
|
17
|
+
width: 100%;
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const InnerContainer = styled.div<
|
|
21
|
+
Pick<InputContainerProps, 'error' | 'variant'>
|
|
22
|
+
>`
|
|
23
|
+
position: relative;
|
|
24
|
+
width: 100%;
|
|
25
|
+
min-height: 58px;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
|
|
29
|
+
border-style: solid;
|
|
30
|
+
border-width: ${(props) => (props?.variant === 'outline' ? sizes[0] : '0px')};
|
|
31
|
+
border-color: ${(props) =>
|
|
32
|
+
props?.error ? colors.border.error : colors.border.active};
|
|
33
|
+
border-radius: ${radii.lg};
|
|
34
|
+
|
|
35
|
+
padding: ${spaces[3]} ${spaces[3]} ${spaces[2]};
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
border-color: ${colors.border.hover};
|
|
39
|
+
cursor: text;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&:focus-within {
|
|
43
|
+
border-color: ${colors.border.focus};
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
export const InputContainer = ({
|
|
48
|
+
error,
|
|
49
|
+
children,
|
|
50
|
+
variant = 'outline',
|
|
51
|
+
}: InputContainerProps) => {
|
|
52
|
+
return (
|
|
53
|
+
<Container>
|
|
54
|
+
<InnerContainer error={error} variant={variant}>
|
|
55
|
+
{children}
|
|
56
|
+
</InnerContainer>
|
|
57
|
+
{typeof error === 'string' && <Label value={error} color="error" />}
|
|
58
|
+
</Container>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import { JutaBrand as Component } from './JutaBrand';
|
|
4
|
+
|
|
5
|
+
const meta: Meta = {
|
|
6
|
+
title: 'Molecules/JutaBrand',
|
|
7
|
+
component: Component,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
const logos = [
|
|
13
|
+
'https://via.placeholder.com/150x60/1e40af/ffffff?text=LENDICOR',
|
|
14
|
+
'https://via.placeholder.com/150x60/1e40af/ffffff?text=Harris+Billings',
|
|
15
|
+
'https://via.placeholder.com/150x60/1e40af/ffffff?text=BDP',
|
|
16
|
+
'https://via.placeholder.com/150x60/1e40af/ffffff?text=cinque',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export const JutaBrand: StoryObj<
|
|
20
|
+
Pick<
|
|
21
|
+
ComponentProps<typeof Component>,
|
|
22
|
+
| 'mainHeader'
|
|
23
|
+
| 'subHeader'
|
|
24
|
+
| 'logos'
|
|
25
|
+
| 'mainHeaderColor'
|
|
26
|
+
| 'subHeaderColor'
|
|
27
|
+
>
|
|
28
|
+
> = {
|
|
29
|
+
args: {
|
|
30
|
+
mainHeader: 'Juta, a trusted brand',
|
|
31
|
+
subHeader: 'With over 170 years of quality solutions in education, legal and business our partners include:',
|
|
32
|
+
logos: logos,
|
|
33
|
+
mainHeaderColor: 'primary',
|
|
34
|
+
subHeaderColor: 'primary',
|
|
35
|
+
},
|
|
36
|
+
argTypes: {
|
|
37
|
+
mainHeader: { control: 'text' },
|
|
38
|
+
subHeader: { control: 'text' },
|
|
39
|
+
logos: { control: 'object' },
|
|
40
|
+
mainHeaderColor: { control: 'text' },
|
|
41
|
+
subHeaderColor: { control: 'text' },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Header } from '../../atoms';
|
|
3
|
+
import { colors } from '../../tokens/colors';
|
|
4
|
+
|
|
5
|
+
type Color = keyof typeof colors.text;
|
|
6
|
+
|
|
7
|
+
const Container = styled.div`
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: 2rem;
|
|
11
|
+
align-items: center;
|
|
12
|
+
text-align: center;
|
|
13
|
+
padding: 3rem 2rem;
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const HeaderWrapper = styled.div`
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 1rem;
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const LogosContainer = styled.div`
|
|
23
|
+
display: flex;
|
|
24
|
+
gap: 3rem;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const LogoWrapper = styled.div`
|
|
31
|
+
max-width: 150px;
|
|
32
|
+
|
|
33
|
+
img {
|
|
34
|
+
width: 100%;
|
|
35
|
+
height: auto;
|
|
36
|
+
display: block;
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
interface JutaBrandProps {
|
|
41
|
+
mainHeader: string;
|
|
42
|
+
subHeader: string;
|
|
43
|
+
logos: string[];
|
|
44
|
+
mainHeaderColor?: Color;
|
|
45
|
+
subHeaderColor?: Color;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const JutaBrand = ({
|
|
49
|
+
mainHeader,
|
|
50
|
+
subHeader,
|
|
51
|
+
logos,
|
|
52
|
+
mainHeaderColor = 'brand',
|
|
53
|
+
subHeaderColor = 'subtle',
|
|
54
|
+
}: JutaBrandProps) => {
|
|
55
|
+
return (
|
|
56
|
+
<Container>
|
|
57
|
+
<HeaderWrapper>
|
|
58
|
+
<Header variant="h1" color={mainHeaderColor}>
|
|
59
|
+
{mainHeader}
|
|
60
|
+
</Header>
|
|
61
|
+
<Header variant="h2" color={subHeaderColor}>
|
|
62
|
+
{subHeader}
|
|
63
|
+
</Header>
|
|
64
|
+
</HeaderWrapper>
|
|
65
|
+
<LogosContainer>
|
|
66
|
+
{logos.map((logo, index) => (
|
|
67
|
+
<LogoWrapper key={index}>
|
|
68
|
+
<img src={logo} alt={`Partner logo ${index + 1}`} />
|
|
69
|
+
</LogoWrapper>
|
|
70
|
+
))}
|
|
71
|
+
</LogosContainer>
|
|
72
|
+
</Container>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { fadeIn } from '../../tokens/animations';
|
|
4
|
+
import { breakpoints } from '../../tokens/breakpoints';
|
|
5
|
+
import { colors } from '../../tokens/colors';
|
|
6
|
+
import { radii } from '../../tokens/radii';
|
|
7
|
+
import { spaces } from '../../tokens/spaces';
|
|
8
|
+
|
|
9
|
+
const positions = ['top', 'center', 'bottom'] as const;
|
|
10
|
+
|
|
11
|
+
type ContentPosition = (typeof positions)[number];
|
|
12
|
+
|
|
13
|
+
interface ModalProps {
|
|
14
|
+
visible: boolean;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
dismissable?: boolean;
|
|
17
|
+
contentPosition?: ContentPosition;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Overlay = styled.div<{ contentPosition: ContentPosition }>`
|
|
22
|
+
height: 100vh;
|
|
23
|
+
width: 100vw;
|
|
24
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
25
|
+
position: fixed;
|
|
26
|
+
inset: 0;
|
|
27
|
+
z-index: 1000;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: ${(props) =>
|
|
30
|
+
props.contentPosition === 'center'
|
|
31
|
+
? 'center'
|
|
32
|
+
: props.contentPosition === 'bottom'
|
|
33
|
+
? 'flex-end'
|
|
34
|
+
: 'flex-start'};
|
|
35
|
+
justify-content: center;
|
|
36
|
+
animation: ${fadeIn} 160ms ease-out;
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const ContentContainer = styled.div`
|
|
40
|
+
position: relative;
|
|
41
|
+
width: min(${breakpoints.sm}, 92vw);
|
|
42
|
+
max-height: 86vh;
|
|
43
|
+
overflow: auto;
|
|
44
|
+
border-radius: ${radii.xl};
|
|
45
|
+
padding: ${spaces[6]};
|
|
46
|
+
background-color: ${colors.background.primary};
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
export const Modal = ({
|
|
50
|
+
visible,
|
|
51
|
+
contentPosition = 'center',
|
|
52
|
+
dismissable = true,
|
|
53
|
+
onClose,
|
|
54
|
+
children,
|
|
55
|
+
}: ModalProps) => {
|
|
56
|
+
function onDismiss() {
|
|
57
|
+
if (dismissable) {
|
|
58
|
+
onClose();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!visible) return <></>;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Overlay contentPosition={contentPosition} onClick={onDismiss}>
|
|
66
|
+
<ContentContainer onClick={(e) => e.stopPropagation()}>
|
|
67
|
+
{children}
|
|
68
|
+
</ContentContainer>
|
|
69
|
+
</Overlay>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { ComponentProps } from "react";
|
|
3
|
+
import { OverviewRowItem as Component } from './OverviewRowItem';
|
|
4
|
+
|
|
5
|
+
const meta: Meta = {
|
|
6
|
+
title: 'Molecules/OverviewRowItem',
|
|
7
|
+
component: Component
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default meta
|
|
11
|
+
|
|
12
|
+
export const OverviewRowItem: StoryObj<ComponentProps<typeof Component>> = {
|
|
13
|
+
args: {
|
|
14
|
+
isComplete: true,
|
|
15
|
+
isMainHeader: true,
|
|
16
|
+
title: 'Property Information'
|
|
17
|
+
},
|
|
18
|
+
argTypes: {
|
|
19
|
+
isComplete: {control: 'boolean'},
|
|
20
|
+
isMainHeader: {control: 'boolean'},
|
|
21
|
+
title: {control: 'text'}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FaCircleCheck, FaCircleExclamation } from "react-icons/fa6"
|
|
2
|
+
import styled from "styled-components"
|
|
3
|
+
import { Header } from "../../atoms"
|
|
4
|
+
import { colors } from "../../tokens/colors"
|
|
5
|
+
import { radii } from "../../tokens/radii"
|
|
6
|
+
import { spaces } from "../../tokens/spaces"
|
|
7
|
+
|
|
8
|
+
const Container = styled.div<{isComplete: boolean}>`
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center ;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
padding: ${spaces[2]} ${spaces[3]};
|
|
13
|
+
background-color: ${props => props.isComplete ? colors.background.blue : colors.background.light};
|
|
14
|
+
border-radius: ${radii.sm};
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
const Row = styled.div`
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: ${spaces[2]};
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
interface OverviewRowItemProps {
|
|
24
|
+
isComplete: boolean
|
|
25
|
+
title: string
|
|
26
|
+
isMainHeader: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const OverviewRowItem = ({isComplete, isMainHeader, title}: OverviewRowItemProps) => {
|
|
30
|
+
|
|
31
|
+
return <Container isComplete={isComplete}>
|
|
32
|
+
<Header variant={isMainHeader ? 'h2' : 'h3'} color={isComplete ? 'secondary' : 'primary'}>{title}</Header>
|
|
33
|
+
|
|
34
|
+
<Row>
|
|
35
|
+
<Header variant='h3' color={isComplete ? 'secondary' : 'primary'}>{isComplete ? 'Complete' : 'Information Outstanding'}</Header>
|
|
36
|
+
{isComplete ? <FaCircleCheck color={colors.background.success}/> : <FaCircleExclamation color={colors.background.error}/>}
|
|
37
|
+
</Row>
|
|
38
|
+
</Container>
|
|
39
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { EmptyState } from '../EmptyState/EmptyState';
|
|
4
|
+
|
|
5
|
+
interface PDFPreviewerProps {
|
|
6
|
+
url: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Container = styled.div`
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const PDFPreviewer = ({ url }: PDFPreviewerProps) => {
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setError(null);
|
|
19
|
+
try {
|
|
20
|
+
new URL(url);
|
|
21
|
+
} catch {
|
|
22
|
+
setError('Invalid preview URL');
|
|
23
|
+
}
|
|
24
|
+
}, [url]);
|
|
25
|
+
|
|
26
|
+
if (typeof error === 'string')
|
|
27
|
+
return (
|
|
28
|
+
<EmptyState message="Failed to load this document" variant="documents" />
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Container>
|
|
33
|
+
<iframe
|
|
34
|
+
src={`${url}#view=FitH&toolbar=1&navpanes=0`}
|
|
35
|
+
title="Document preview"
|
|
36
|
+
allow="clipboard-read; clipboard-write"
|
|
37
|
+
referrerPolicy="no-referrer"
|
|
38
|
+
style={{ width: '100%', height: '100%', border: '0' }}
|
|
39
|
+
/>
|
|
40
|
+
</Container>
|
|
41
|
+
);
|
|
42
|
+
};
|