├── .commitlintrc.json ├── .eslintrc.json ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── commitlint.config.js ├── components ├── Accordion │ ├── AccordionItem │ │ ├── index.tsx │ │ └── props.ts │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss ├── Avatar │ ├── index.tsx │ └── props.ts ├── AvatarGroup │ └── index.tsx ├── Badge │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss ├── Breadcrumb │ ├── index.tsx │ └── props.ts ├── Button │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss ├── Calendar │ ├── Cells.tsx │ ├── Days.tsx │ ├── Header │ │ ├── index.tsx │ │ └── props.ts │ ├── index.tsx │ └── props.ts ├── Card │ ├── index.tsx │ └── props.ts ├── Container │ └── index.tsx ├── Divider │ ├── index.tsx │ └── props.ts ├── Icon │ ├── index.tsx │ └── props.ts ├── If │ └── index.tsx ├── Inputs │ ├── Calendar │ │ ├── Body │ │ │ ├── Days.tsx │ │ │ ├── Header │ │ │ │ ├── index.tsx │ │ │ │ └── props.ts │ │ │ ├── index.tsx │ │ │ └── props.ts │ │ ├── Header │ │ │ ├── index.tsx │ │ │ └── props.ts │ │ └── index.tsx │ ├── Checkbox │ │ ├── index.tsx │ │ ├── props.ts │ │ └── styles.module.scss │ ├── Error │ │ └── index.tsx │ ├── Input │ │ ├── index.tsx │ │ ├── props.ts │ │ └── styles.module.scss │ ├── Label │ │ ├── index.tsx │ │ └── props.ts │ ├── Radio │ │ ├── index.tsx │ │ ├── props.ts │ │ └── styles.module.scss │ ├── Range │ │ ├── index.tsx │ │ ├── props.ts │ │ └── styles.module.scss │ ├── Select │ │ ├── index.tsx │ │ └── props.ts │ ├── Switch │ │ ├── index.tsx │ │ ├── props.ts │ │ └── styles.module.scss │ └── props.ts ├── Loading │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss ├── Modal │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss ├── Pagination │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss ├── Spinner │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss ├── SwitchCase │ ├── index.tsx │ └── props.ts ├── Tabs │ ├── index.tsx │ └── props.ts ├── Text │ ├── index.tsx │ ├── props.ts │ └── styles.module.scss └── Toast │ └── index.tsx ├── helper └── calendar.ts ├── hooks ├── useOnClickOutside.tsx ├── usePagination.tsx └── useToggle.tsx ├── icomoon ├── Read Me.txt ├── demo-files │ ├── demo.css │ └── demo.js ├── demo.html ├── fonts │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff ├── selection.json └── style.css ├── lint-staged.config.js ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── form.tsx └── index.tsx ├── postcss.config.js ├── public ├── fonts │ └── font-icons │ │ ├── icomoon.eot │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ └── icomoon.woff └── images │ └── banner.png ├── styles ├── font-icons.scss └── globals.scss ├── tailwind.config.js ├── tsconfig.json ├── utils ├── dateTime.ts └── validations │ ├── messages │ └── messages.ts │ └── regex │ └── regex.ts └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2020, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true 7 | } 8 | }, 9 | "settings": { 10 | "react": { 11 | "version": "detect" 12 | }, 13 | "import/resolver": { 14 | "node": { 15 | "extensions": [".ts", ".tsx"] 16 | } 17 | } 18 | }, 19 | "plugins": ["@typescript-eslint","tailwindcss"], 20 | "extends": [ 21 | "next/core-web-vitals", 22 | "plugin:@typescript-eslint/recommended", 23 | "plugin:security/recommended", 24 | "plugin:tailwindcss/recommended" 25 | /*"prettier", 26 | "plugin:prettier/recommended"*/ 27 | ], 28 | "rules": { 29 | "no-console": "error", 30 | "no-dupe-else-if": "error", 31 | "no-self-compare": "error", 32 | "@typescript-eslint/no-unused-vars": "error", 33 | "@typescript-eslint/no-explicit-any": "warn", 34 | "react/react-in-jsx-scope": "off", 35 | "@typescript-eslint/no-non-null-assertion": "off", 36 | "react/jsx-filename-extension": [ 37 | 1, 38 | { 39 | "extensions": [".ts", ".tsx", ".js", ".jsx"] 40 | } 41 | ], 42 | "react/jsx-props-no-spreading": "off", 43 | "import/extensions": [ 44 | "error", 45 | "ignorePackages", 46 | { 47 | "js": "never", 48 | "jsx": "never", 49 | "ts": "never", 50 | "tsx": "never" 51 | } 52 | ], 53 | "jsx-a11y/anchor-is-valid": [ 54 | "error", 55 | { 56 | "components": ["Link"], 57 | "specialLink": ["hrefLeft", "hrefRight"], 58 | "aspects": ["invalidHref", "preferButton"] 59 | } 60 | ], 61 | "no-nested-ternary": "off", 62 | "import/prefer-default-export": "off" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | .cache 3 | public 4 | node_modules 5 | next-env.d.ts 6 | next.config.js 7 | yarn.lock 8 | postcss.config.js 9 | *.json 10 | *.md 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 80, 4 | "endOfLine": "auto", 5 | "arrowParens": "avoid", 6 | "trailingComma": "es5", 7 | "semi": true, 8 | "useTabs": false, 9 | "singleQuote": true, 10 | "jsxSingleQuote": true, 11 | "bracketSpacing": true 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ali Ehyaie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /components/Accordion/AccordionItem/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, type FC,type MouseEvent } from 'react'; 2 | import Text from '../../Text'; 3 | import Icon from '../../Icon'; 4 | import Accordion from "../index"; 5 | import classes from '../styles.module.scss'; 6 | import type { AccordionItemProps } from './props'; 7 | 8 | const AccordionItem: FC = ({ 9 | accordionItem, 10 | isMultiple, 11 | isActive = false, 12 | toggleOnlyOneAccordionItemHandler, 13 | itemClassName, 14 | }) => { 15 | const [isVisible, setIsVisible] = useState(isActive); 16 | useEffect(() => { 17 | setIsVisible(isActive); 18 | }, [isActive]); 19 | const toggleAccordionItemHandler = ( 20 | e: MouseEvent 21 | ) => { 22 | e.stopPropagation(); 23 | setIsVisible(prevState => !prevState); 24 | }; 25 | return ( 26 |
27 |
30 | isMultiple 31 | ? toggleAccordionItemHandler(e) 32 | : toggleOnlyOneAccordionItemHandler(accordionItem.id) 33 | } 34 | > 35 | {accordionItem.title} 36 | 42 |
43 | 49 | {accordionItem.content} 50 | 51 | {accordionItem?.children && ( 52 | 58 | )} 59 |
60 | ); 61 | }; 62 | 63 | export default AccordionItem; 64 | -------------------------------------------------------------------------------- /components/Accordion/AccordionItem/props.ts: -------------------------------------------------------------------------------- 1 | import {ReactElement} from "react"; 2 | import type {AccordionProps} from "../props"; 3 | 4 | type IAccordionItem = { 5 | id: string | number; 6 | title: string | ReactElement; 7 | content: string | ReactElement; 8 | children?: IAccordionItem[]; 9 | }; 10 | 11 | export interface AccordionItemProps 12 | extends Pick { 13 | accordionItem: IAccordionItem; 14 | isActive: boolean; 15 | toggleOnlyOneAccordionItemHandler: (id: IAccordionItem['id']) => void; 16 | } 17 | -------------------------------------------------------------------------------- /components/Accordion/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState,Fragment, type FC } from 'react'; 2 | import AccordionItem from './AccordionItem'; 3 | import classes from './styles.module.scss'; 4 | import type { AccordionProps } from './props'; 5 | 6 | const Accordion: FC = ({ 7 | accordionItems, 8 | isMultiple = true, 9 | className, 10 | itemClassName, 11 | }) => { 12 | const [selectedAccordionItemId, setSelectedAccordionItemId] = useState< 13 | null | number | string 14 | >(null); 15 | const toggleOnlyOneAccordionItemHandler = ( 16 | accordionItemId: number | string 17 | ) => { 18 | if (selectedAccordionItemId === accordionItemId) { 19 | setSelectedAccordionItemId(null); 20 | return; 21 | } 22 | setSelectedAccordionItemId(accordionItemId); 23 | }; 24 | 25 | return ( 26 |
27 | {accordionItems.map(accordionItem => { 28 | return ( 29 | 30 | 41 | 42 | ); 43 | })} 44 |
45 | ); 46 | }; 47 | 48 | export default Accordion; -------------------------------------------------------------------------------- /components/Accordion/props.ts: -------------------------------------------------------------------------------- 1 | import type { AccordionItemProps } from './AccordionItem/props'; 2 | 3 | export interface AccordionProps { 4 | accordionItems: AccordionItemProps['accordionItem'][]; 5 | isMultiple?: boolean; 6 | className?: string; 7 | itemClassName?: string; 8 | } 9 | -------------------------------------------------------------------------------- /components/Accordion/styles.module.scss: -------------------------------------------------------------------------------- 1 | .accordion { 2 | @apply w-full flex flex-col gap-4; 3 | } 4 | 5 | .item { 6 | @apply rounded-lg border border-primary px-2.5 py-2.5; 7 | } 8 | 9 | .title { 10 | @apply flex justify-between items-center cursor-pointer; 11 | } 12 | 13 | .icon{ 14 | @apply transition-all duration-300 rotate-0 text-primary; 15 | } 16 | 17 | .icon.show { 18 | @apply rotate-180; 19 | } 20 | 21 | .content { 22 | @apply overflow-hidden max-h-0 transition-all duration-500; 23 | transition-timing-function: cubic-bezier(0, 1, 0, 1); 24 | } 25 | 26 | .content.show { 27 | @apply max-h-screen duration-[1500ms] ease-in-out; 28 | } 29 | -------------------------------------------------------------------------------- /components/Avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import {useState, type FC} from 'react'; 3 | import Icon from '../Icon'; 4 | import Text from '../Text'; 5 | import { twMerge } from 'tailwind-merge'; 6 | import type { AvatarProps } from './props'; 7 | 8 | const Avatar: FC = ({ 9 | variant = 'circle', 10 | imgSrc, 11 | label, 12 | alt, 13 | iconName = 'icon-person', 14 | className = '', 15 | }) => { 16 | const [showFallback, setShowFallback] = useState(false); 17 | return ( 18 |
24 | {imgSrc && !showFallback ? ( 25 | setShowFallback(true)} 28 | fill 29 | style={{ objectFit: 'cover' }} 30 | alt={alt || 'avatar image'} 31 | /> 32 | ) : label ? ( 33 | 34 | {label} 35 | 36 | ) : ( 37 | 38 | )} 39 |
40 | ); 41 | }; 42 | 43 | export default Avatar; 44 | -------------------------------------------------------------------------------- /components/Avatar/props.ts: -------------------------------------------------------------------------------- 1 | export interface AvatarProps { 2 | variant?: 'circle' | 'square'; 3 | imgSrc?: string; 4 | iconName?: string; 5 | label?: string; 6 | alt?: string; 7 | className?: string; 8 | } 9 | -------------------------------------------------------------------------------- /components/AvatarGroup/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren } from 'react'; 2 | 3 | const AvatarGroup: FC = ({ children }) => { 4 | return ( 5 |
6 | {children} 7 |
8 | ); 9 | }; 10 | 11 | export default AvatarGroup; 12 | -------------------------------------------------------------------------------- /components/Badge/index.tsx: -------------------------------------------------------------------------------- 1 | import classes from './styles.module.scss'; 2 | import type { FC } from 'react'; 3 | import type { BadgeProps } from './props'; 4 | 5 | const Badge: FC = ({ 6 | position = 'bottomLeft', 7 | value = '', 8 | severity = 'success', 9 | isAbsolute, 10 | type = 'normal', 11 | className = '', 12 | }) => { 13 | return ( 14 | 23 | {value} 24 | 25 | ); 26 | }; 27 | 28 | export default Badge; 29 | -------------------------------------------------------------------------------- /components/Badge/props.ts: -------------------------------------------------------------------------------- 1 | export interface BadgeProps { 2 | type?: 'circular' | 'normal'; 3 | isAbsolute?: boolean; 4 | position?: 'topRight' | 'topLeft' | 'bottomRight' | 'bottomLeft'; 5 | value?: string; 6 | severity?: 'warning' | 'info' | 'success' | 'error'; 7 | className?: string; 8 | } 9 | -------------------------------------------------------------------------------- /components/Badge/styles.module.scss: -------------------------------------------------------------------------------- 1 | .badge { 2 | @apply inline-block text-sm flex justify-center items-center text-white; 3 | } 4 | 5 | .normal { 6 | @apply rounded px-2 py-1; 7 | } 8 | 9 | .circular { 10 | @apply rounded-full w-4 h-4 p-2; 11 | } 12 | 13 | .absoluteBadge { 14 | @apply absolute; 15 | } 16 | 17 | .topRight { 18 | @apply -top-2 -right-2; 19 | } 20 | 21 | .topLeft { 22 | @apply -top-2 -left-2; 23 | } 24 | 25 | .bottomRight { 26 | @apply -bottom-2 -right-2; 27 | } 28 | 29 | .bottomLeft { 30 | @apply -bottom-2 -left-2; 31 | } 32 | 33 | .success { 34 | @apply bg-success; 35 | } 36 | 37 | .warning { 38 | @apply bg-warning; 39 | } 40 | 41 | .error { 42 | @apply bg-error; 43 | } 44 | 45 | .info { 46 | @apply bg-info; 47 | } 48 | -------------------------------------------------------------------------------- /components/Breadcrumb/index.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../Text'; 2 | import Button from '../Button'; 3 | import Icon from '../Icon'; 4 | import type { BreadcrumbProps } from './props'; 5 | import type { FC } from 'react'; 6 | 7 | const Breadcrumbs: FC = ({ 8 | breadCrumbs, 9 | separator = 'icon-chevron-right', 10 | className = '', 11 | }) => ( 12 |
13 | {breadCrumbs.map((breadCrumb, index) => { 14 | if (breadCrumbs.length - 1 === index) 15 | return ( 16 | 17 | {breadCrumb.label} 18 | 19 | ); 20 | return ( 21 |
25 |
37 | ); 38 | })} 39 |
40 | ); 41 | 42 | export default Breadcrumbs; 43 | -------------------------------------------------------------------------------- /components/Breadcrumb/props.ts: -------------------------------------------------------------------------------- 1 | export interface BreadcrumbProps { 2 | breadCrumbs: { 3 | label: string; 4 | url: string; 5 | }[]; 6 | className?: string; 7 | separator?: string; 8 | } 9 | -------------------------------------------------------------------------------- /components/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import Loading from '../Loading'; 3 | import Icon from "../Icon"; 4 | import classes from './styles.module.scss'; 5 | import type { FC } from 'react'; 6 | import type {ButtonProps} from './props'; 7 | 8 | const Button: FC = ({ 9 | label, 10 | leadingIconName, 11 | helperIconName, 12 | iconPos ="right", 13 | color = "primary", 14 | isLoading, 15 | className, 16 | variant = "filled", 17 | shape = "normal", 18 | isLink, 19 | href= "", 20 | ...props 21 | }) => { 22 | const ButtonEl = ( 23 | 48 | ); 49 | return isLink ? {ButtonEl} : ButtonEl; 50 | }; 51 | 52 | export default Button; 53 | -------------------------------------------------------------------------------- /components/Button/props.ts: -------------------------------------------------------------------------------- 1 | import type {ButtonHTMLAttributes} from "react"; 2 | 3 | export interface ButtonProps extends ButtonHTMLAttributes { 4 | label?: string; 5 | leadingIconName?: string; 6 | helperIconName?: string; 7 | iconPos?: 'right' | 'left'; 8 | color?: 'primary' | 'text' | 'secondary' | 'custom'; 9 | isLoading?: boolean; 10 | className?: string; 11 | variant?: 'filled' | 'outlined' | 'tonal' | 'text'; 12 | isLink?: boolean; 13 | href?: string; 14 | shape?: 'normal' | 'chips'; 15 | } 16 | -------------------------------------------------------------------------------- /components/Button/styles.module.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | @apply transition-all duration-200 text-sm font-medium flex justify-center items-center gap-2 px-4; 3 | 4 | &.filled, 5 | &.outlined, 6 | &.tonal { 7 | &.normal { 8 | @apply rounded-xl min-w-[84px] h-[46px] text-sm; 9 | } 10 | 11 | &.chips { 12 | @apply rounded-xl min-w-[110px] h-[32px] text-xs; 13 | } 14 | 15 | &.leading { 16 | @apply min-w-[46px] h-[46px] rounded-xl; 17 | } 18 | } 19 | 20 | &.text { 21 | &.normal { 22 | @apply text-sm; 23 | } 24 | 25 | &.chips { 26 | @apply text-xs; 27 | } 28 | } 29 | 30 | &.primary { 31 | &.filled { 32 | @apply bg-primary text-white text-sm; 33 | 34 | &:hover { 35 | @apply bg-primary-dark; 36 | } 37 | 38 | &:active { 39 | @apply bg-primary-darker; 40 | } 41 | 42 | &:disabled { 43 | @apply text-disabled bg-grey-3; 44 | } 45 | } 46 | 47 | &.outlined { 48 | @apply bg-transparent border text-primary border-primary; 49 | 50 | &:hover { 51 | @apply text-primary-dark border-primary-dark; 52 | } 53 | 54 | &:active { 55 | @apply text-primary-dark border-primary-dark; 56 | } 57 | 58 | &:disabled { 59 | @apply text-disabled border-grey-3; 60 | } 61 | } 62 | 63 | &.tonal { 64 | @apply bg-info-bg text-primary; 65 | 66 | &:hover { 67 | @apply bg-info-bg-dark text-primary-dark; 68 | } 69 | 70 | &:active { 71 | @apply bg-info-bg-darker text-primary-darker; 72 | } 73 | 74 | &:disabled { 75 | @apply text-disabled bg-grey-3; 76 | } 77 | } 78 | 79 | &.text { 80 | @apply text-primary; 81 | 82 | &:hover { 83 | @apply text-primary-dark; 84 | } 85 | 86 | &:active { 87 | @apply text-primary-darker; 88 | } 89 | 90 | &:disabled { 91 | @apply text-disabled; 92 | } 93 | } 94 | } 95 | 96 | &.text { 97 | 98 | &.outlined { 99 | @apply bg-transparent border text-primary border-primary; 100 | 101 | &:hover { 102 | @apply text-primary-dark border-primary-dark; 103 | } 104 | 105 | &:active { 106 | @apply text-primary-dark border-primary-dark; 107 | } 108 | 109 | &:disabled { 110 | @apply text-disabled border-grey-3; 111 | } 112 | } 113 | 114 | &.tonal { 115 | @apply bg-info-bg text-primary; 116 | 117 | &:hover { 118 | @apply bg-info-bg-dark text-primary-dark; 119 | } 120 | 121 | &:active { 122 | @apply bg-info-bg-darker text-primary-darker; 123 | } 124 | 125 | &:disabled { 126 | @apply text-disabled bg-grey-3; 127 | } 128 | } 129 | 130 | &.text { 131 | @apply text-grey-7; 132 | 133 | &:hover { 134 | @apply text-grey-5; 135 | } 136 | 137 | &:active { 138 | @apply text-text; 139 | } 140 | 141 | &:disabled { 142 | @apply text-disabled; 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /components/Calendar/Cells.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useState, type FC } from 'react'; 2 | import Text from '../Text'; 3 | import { DateTimeUtils } from '../../utils/dateTime'; 4 | import { getAllDates, getFormattedDateObject } from '../../helper/calendar'; 5 | import type { CalendarProps } from './props'; 6 | import type { Dayjs } from 'dayjs'; 7 | 8 | const CalendarCells: FC>> = ({ 9 | date, 10 | events, 11 | }) => { 12 | const dateTimeUtils = new DateTimeUtils(); 13 | const [datesOfCurrentMonth, setDatesOfCurrentMonth] = useState< 14 | Dayjs[] | [] 15 | >([]); 16 | 17 | useEffect(() => { 18 | setDatesOfCurrentMonth(getAllDates(date)); 19 | }, [date]); 20 | 21 | return ( 22 | <> 23 | {datesOfCurrentMonth.map(date => { 24 | const formattedDate: ReturnType< 25 | typeof getFormattedDateObject 26 | > & { event?: CalendarProps['events'][0] } = 27 | getFormattedDateObject(date); 28 | formattedDate.event = events.find(event => 29 | date.isSame(event.date, 'day') 30 | ); 31 | 32 | return ( 33 |
41 |
42 |
43 | {`${formattedDate.gDay}/`} 54 | 65 | {formattedDate.day} 66 | 67 |
68 | {formattedDate.event ? ( 69 | <> 70 |
75 | 76 | {formattedDate.event.title}: 77 | {dateTimeUtils.getFormattedDate( 78 | formattedDate.event.date, 79 | 'HH:mm' 80 | )} 81 | 82 |
83 | 84 | ) : null} 85 |
86 |
87 | ); 88 | })} 89 | 90 | ); 91 | }; 92 | 93 | export default CalendarCells; 94 | -------------------------------------------------------------------------------- /components/Calendar/Days.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../Text'; 2 | import { DateTimeUtils } from '../../utils/dateTime'; 3 | 4 | const CalendarDays = () => { 5 | const dateTimeUtils = new DateTimeUtils(); 6 | return ( 7 | <> 8 | {dateTimeUtils.getDaysNameOfWeek().map(day => ( 9 |
13 | {day} 14 |
15 | ))} 16 | 17 | ); 18 | }; 19 | 20 | export default CalendarDays; 21 | -------------------------------------------------------------------------------- /components/Calendar/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import Icon from '../../Icon'; 2 | import Text from '../../Text'; 3 | import { DateTimeUtils } from '../../../utils/dateTime'; 4 | import type { FC } from 'react'; 5 | import type { CalendarHeaderProps } from './props'; 6 | 7 | const CalendarHeader: FC = ({ date, setDate }) => { 8 | const dateTimeUtils = new DateTimeUtils(); 9 | const monthBeforeOrEqualToCurrentMonth = () => 10 | date.month() <= dateTimeUtils.getNow().month(); 11 | const next = () => { 12 | const nextMonthDate = dateTimeUtils.getNextMonthDate(date); 13 | setDate(nextMonthDate); 14 | }; 15 | const prev = () => { 16 | if (monthBeforeOrEqualToCurrentMonth()) { 17 | return; 18 | } 19 | const prevMonthDate = dateTimeUtils.getPrevMonthDate(date); 20 | setDate(prevMonthDate); 21 | }; 22 | 23 | return ( 24 |
25 |
26 |
35 | 43 |
44 |
45 | 46 | {dateTimeUtils.getFormattedDate(date, 'MMMM YYYY')} 47 | 48 |
49 |
53 | 54 |
55 |
56 |
57 | ); 58 | }; 59 | 60 | export default CalendarHeader; 61 | -------------------------------------------------------------------------------- /components/Calendar/Header/props.ts: -------------------------------------------------------------------------------- 1 | import type { Dispatch, SetStateAction } from 'react'; 2 | import type { Dayjs } from 'dayjs'; 3 | 4 | export interface CalendarHeaderProps { 5 | date: Dayjs; 6 | setDate: Dispatch>; 7 | } 8 | -------------------------------------------------------------------------------- /components/Calendar/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import CalendarHeader from './Header'; 3 | import CalendarDays from './Days'; 4 | import CalendarCells from './Cells'; 5 | import { DateTimeUtils } from '../../utils/dateTime'; 6 | import type { CalendarProps } from './props'; 7 | import type { FC } from 'react'; 8 | 9 | const Calendar: FC = ({ date, events }) => { 10 | const dateTimeUtils = new DateTimeUtils(); 11 | const [currentDate, setCurrentDate] = useState(dateTimeUtils.getDate(date)); 12 | 13 | return ( 14 |
15 | 16 |
17 |
18 |
26 | 27 | 28 |
29 |
30 |
31 |
32 | ); 33 | }; 34 | 35 | export default Calendar; 36 | -------------------------------------------------------------------------------- /components/Calendar/props.ts: -------------------------------------------------------------------------------- 1 | import type { Dayjs } from 'dayjs'; 2 | export interface CalendarProps { 3 | date?: Dayjs; 4 | events: { 5 | title: string; 6 | date: Dayjs | number; 7 | }[]; 8 | } 9 | -------------------------------------------------------------------------------- /components/Card/index.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../Text'; 2 | import { twMerge } from 'tailwind-merge'; 3 | import type { FC, PropsWithChildren } from 'react'; 4 | import type {CardProps} from './props'; 5 | 6 | const Card: FC = ({ 7 | footer, 8 | header, 9 | subtitle, 10 | title, 11 | children, 12 | className, 13 | }) => { 14 | return ( 15 |
21 | {header &&
{header}
} 22 |
23 | {(title || subtitle) && ( 24 |
25 | {title && {title}} 26 | {subtitle && {subtitle}} 27 |
28 | )} 29 |
{children}
30 |
31 | {footer &&
{footer}
} 32 |
33 | ); 34 | }; 35 | 36 | export default Card; 37 | -------------------------------------------------------------------------------- /components/Card/props.ts: -------------------------------------------------------------------------------- 1 | import type {ReactElement} from "react"; 2 | 3 | export interface CardProps { 4 | title?: string; 5 | subtitle?: string; 6 | header?: ReactElement; 7 | footer?: ReactElement; 8 | className?: string; 9 | } 10 | -------------------------------------------------------------------------------- /components/Container/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren } from 'react'; 2 | 3 | const Container: FC = ({ children }) => { 4 | return ( 5 |
{children}
6 | ); 7 | }; 8 | 9 | export default Container; 10 | -------------------------------------------------------------------------------- /components/Divider/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import type { DividerProps } from './props'; 3 | 4 | const Divider: FC = ({ 5 | layout = 'horizontal', 6 | type = 'solid', 7 | }) => { 8 | return ( 9 | 22 | ); 23 | }; 24 | 25 | export default Divider; 26 | -------------------------------------------------------------------------------- /components/Divider/props.ts: -------------------------------------------------------------------------------- 1 | export interface DividerProps { 2 | layout?: 'horizontal' | 'vertical'; 3 | type?: 'solid' | 'dashed' | 'dotted'; 4 | } 5 | -------------------------------------------------------------------------------- /components/Icon/index.tsx: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge'; 2 | import type { FC } from 'react'; 3 | import type {IconProps} from './props'; 4 | 5 | const Icon: FC = ({ iconName, className = '', onClick }) => ( 6 | 10 | ); 11 | 12 | export default Icon; 13 | -------------------------------------------------------------------------------- /components/Icon/props.ts: -------------------------------------------------------------------------------- 1 | import type {MouseEvent} from "react"; 2 | 3 | export interface IconProps { 4 | iconName: string; 5 | className?: string; 6 | onClick?: (e: MouseEvent) => void; 7 | } 8 | -------------------------------------------------------------------------------- /components/If/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Children, 3 | type FC, 4 | type PropsWithChildren, 5 | type ReactElement, 6 | } from 'react'; 7 | 8 | const Then: FC = ({ children }) => <>{children}; 9 | 10 | const Else: FC = ({ children }) => <>{children}; 11 | 12 | const If: FC = ({ 13 | children, 14 | condition, 15 | }) => { 16 | if (!children) return null; 17 | 18 | const childrenArray = Children.toArray(children) as ReactElement[]; 19 | 20 | // Render Then block if the condition is truthy 21 | if (condition) { 22 | return <>{childrenArray.find(child => child.type === Then)}; 23 | } 24 | 25 | // Render Else block if the condition is falsy 26 | return <>{childrenArray.find(child => child.type === Else)}; 27 | }; 28 | 29 | export { If, Then, Else }; 30 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/Body/Days.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../../../Text'; 2 | import { If, Then } from '../../../If'; 3 | import { DateTimeUtils } from '../../../../utils/dateTime'; 4 | import { getFormattedDateObject } from '../../../../helper/calendar'; 5 | import type { FC } from 'react'; 6 | import type { CalendarBodyProps } from './props'; 7 | 8 | const CalendarBodyDays: FC< 9 | Pick< 10 | CalendarBodyProps, 11 | | 'isGregory' 12 | | 'datesOfCurrentMonth' 13 | | 'dateRange' 14 | | 'setDateRange' 15 | | 'date' 16 | > 17 | > = ({ isGregory, datesOfCurrentMonth, dateRange, setDateRange, date }) => { 18 | const dateTimeUtils = new DateTimeUtils( 19 | isGregory ? 'gregory' : 'jalali', 20 | isGregory ? 'en' : 'fa' 21 | ); 22 | 23 | return ( 24 |
25 | {dateTimeUtils.getDaysNameOfWeek('dd').map(dayOfWeek => ( 26 | 27 | {dayOfWeek} 28 | 29 | ))} 30 | {datesOfCurrentMonth.map(dateOfCurrentMonth => { 31 | const formattedDate = 32 | getFormattedDateObject(dateOfCurrentMonth); 33 | return ( 34 |
{ 36 | if ( 37 | formattedDate.hasPassed || 38 | formattedDate.month !== date.month() 39 | ) 40 | return; 41 | if (!dateRange.length || dateRange.length === 2) { 42 | setDateRange([dateOfCurrentMonth]); 43 | } else { 44 | if ( 45 | dateTimeUtils.isBefore( 46 | dateOfCurrentMonth, 47 | dateRange[0] 48 | ) 49 | ) { 50 | setDateRange([dateOfCurrentMonth]); 51 | } else { 52 | setDateRange([ 53 | ...dateRange, 54 | dateOfCurrentMonth, 55 | ]); 56 | } 57 | } 58 | }} 59 | key={dateOfCurrentMonth.toString()} 60 | className={`justify-self-stretch p-1.5 text-center 61 | ${ 62 | formattedDate.isToday && 63 | dateRange?.[0] !== 64 | dateOfCurrentMonth 65 | ? 'rounded-md border border-warning' 66 | : '' 67 | } 68 | ${ 69 | dateTimeUtils.isSame( 70 | dateOfCurrentMonth, 71 | dateRange?.[0] 72 | ) && 73 | formattedDate.month === 74 | date.month() 75 | ? 'rounded-r-sm bg-warning' 76 | : dateTimeUtils.isSame( 77 | dateOfCurrentMonth, 78 | dateRange?.[1] 79 | ) && 80 | formattedDate.month === 81 | date.month() 82 | ? 'rounded-l-sm bg-warning' 83 | : '' 84 | } 85 | ${ 86 | dateRange.length === 2 && 87 | dateTimeUtils.isBetween( 88 | dateOfCurrentMonth, 89 | dateRange?.[0], 90 | dateRange?.[1] 91 | ) && 92 | formattedDate.month === 93 | date.month() 94 | ? 'bg-warning-bg' 95 | : '' 96 | } 97 | `} 98 | > 99 | 100 | 101 | 112 | {formattedDate.day} 113 | 114 | 115 | 116 |
117 | ); 118 | })} 119 |
120 | ); 121 | }; 122 | 123 | export default CalendarBodyDays; 124 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/Body/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../../../../Text'; 2 | import Icon from "../../../../Icon"; 3 | import { If, Then } from '../../../../If'; 4 | import { DateTimeUtils } from '../../../../../utils/dateTime'; 5 | import type { FC } from 'react'; 6 | import type {CalendarBodyHeaderProps} from './props'; 7 | 8 | const CalendarBodyHeader: FC = ({ 9 | setDate, 10 | date, 11 | isGregory, 12 | monthPosition = 'middle', 13 | }) => { 14 | const dateTimeUtils = new DateTimeUtils( 15 | isGregory ? 'gregory' : 'jalali', 16 | isGregory ? 'en' : 'fa' 17 | ); 18 | 19 | const monthBeforeOrEqualToCurrentMonth = () => 20 | date.month() <= dateTimeUtils.getNow().month(); 21 | 22 | const next = () => { 23 | setDate(date); 24 | }; 25 | 26 | const prev = () => { 27 | if (monthBeforeOrEqualToCurrentMonth()) { 28 | return; 29 | } 30 | const prevMonthDate = dateTimeUtils.getPrevMonthDate(date); 31 | setDate(prevMonthDate); 32 | }; 33 | 34 | return ( 35 |
36 | 37 | 38 | 43 | 44 | 45 | 46 | {date.format('MMMM')} 47 | 48 | 49 | 50 | 55 | 56 | 57 |
58 | ); 59 | }; 60 | 61 | export default CalendarBodyHeader; 62 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/Body/Header/props.ts: -------------------------------------------------------------------------------- 1 | import type { CalendarBodyProps } from '../props'; 2 | 3 | export interface CalendarBodyHeaderProps 4 | extends Pick { 5 | monthPosition?: 'first' | 'last' | 'middle'; 6 | } 7 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/Body/index.tsx: -------------------------------------------------------------------------------- 1 | import CalendarBodyDays from './Days'; 2 | import CalendarBodyHeader from './Header'; 3 | import { DateTimeUtils } from '../../../../utils/dateTime'; 4 | import { getAllDates } from '../../../../helper/calendar'; 5 | import type { FC } from 'react'; 6 | import type {CalendarBodyProps} from "./props"; 7 | 8 | const CalendarBody: FC = ({ 9 | numberOfMonths = 1, 10 | setDate, 11 | date, 12 | isGregory, 13 | datesOfCurrentMonth, 14 | dateRange, 15 | setDateRange, 16 | }) => { 17 | const dateTimeUtils = new DateTimeUtils( 18 | isGregory ? 'gregory' : 'jalali', 19 | isGregory ? 'en' : 'fa' 20 | ); 21 | 22 | return ( 23 |
24 | {[...Array(numberOfMonths)].map((_, i) => { 25 | if (i === 0) { 26 | return ( 27 |
31 | 37 | 44 |
45 | ); 46 | } else if (i === numberOfMonths - 1) { 47 | const nextMonthDate = dateTimeUtils.getNextMonthDate(date); 48 | return ( 49 |
53 | 59 | 66 |
67 | ); 68 | } else { 69 | return <>; 70 | } 71 | })} 72 | {/* */} 73 |
74 | ); 75 | }; 76 | 77 | export default CalendarBody; 78 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/Body/props.ts: -------------------------------------------------------------------------------- 1 | import type {Dispatch, SetStateAction} from "react"; 2 | import type { Dayjs } from 'dayjs'; 3 | import type { CalendarHeaderProps } from '../Header/props'; 4 | 5 | export interface CalendarBodyProps 6 | extends Pick { 7 | numberOfMonths?: number; 8 | datesOfCurrentMonth: Dayjs[]; 9 | date: Dayjs; 10 | dateRange: [Dayjs] | [Dayjs, Dayjs] | []; 11 | setDateRange: Dispatch< 12 | SetStateAction<[Dayjs] | [Dayjs, Dayjs] | []> 13 | >; 14 | } 15 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../../../Text'; 2 | import Icon from "../../../Icon"; 3 | import { If, Then, Else } from '../../../If'; 4 | import { DateTimeUtils } from '../../../../utils/dateTime'; 5 | import type { FC } from 'react'; 6 | import type { CalendarHeaderProps } from './props'; 7 | 8 | const CalendarHeader: FC = ({ 9 | calendarToggle, 10 | setDate, 11 | isGregory, 12 | }) => { 13 | const dateTimeUtils = new DateTimeUtils( 14 | isGregory ? 'gregory' : 'jalali', 15 | isGregory ? 'en' : 'fa' 16 | ); 17 | return ( 18 |
19 |
{ 21 | setDate(dateTimeUtils.getNow()); 22 | }} 23 | > 24 | برو به امروز 25 |
26 |
27 | 28 | 29 | 30 | تقویم شمسی 31 | 32 | 33 | تقویم میلادی 34 | 35 | 36 |
37 |
38 | ); 39 | }; 40 | 41 | export default CalendarHeader; 42 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/Header/props.ts: -------------------------------------------------------------------------------- 1 | import type { Dispatch, SetStateAction } from 'react'; 2 | import type { Dayjs } from 'dayjs'; 3 | 4 | export interface CalendarHeaderProps { 5 | calendarToggle: () => void; 6 | isGregory: boolean; 7 | setDate: Dispatch>; 8 | } 9 | -------------------------------------------------------------------------------- /components/Inputs/Calendar/index.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef, useState, type FC} from 'react'; 2 | import Input from '../Input'; 3 | import CalendarHeader from './Header'; 4 | import { If, Then } from '../../If'; 5 | import CalendarBody from './Body'; 6 | import useToggle from '../../../hooks/useToggle'; 7 | import useOnClickOutside from '../../../hooks/useOnClickOutside'; 8 | import { DateTimeUtils } from '../../../utils/dateTime'; 9 | import { getAllDates } from '../../../helper/calendar'; 10 | import type { InputProps } from '../Input/props'; 11 | import type { Dayjs } from 'dayjs'; 12 | 13 | interface IInputCalendar extends Omit { 14 | onChange: (date: Dayjs[]) => void; 15 | } 16 | 17 | // TODO: This component needs some improvements 18 | // 1: Make the 'onChange' prop consistent with other inputs if possible 19 | // 2: Add a single date picker feature 20 | // 3: Add a multiple date picker feature 21 | // 4: Debug the multi-month calendar 22 | // 5: Make it responsive 23 | 24 | const Calendar: FC = props => { 25 | const [open, toggle, setOpen] = useToggle(false); 26 | const calendarRef = useRef(null); 27 | useOnClickOutside(calendarRef, () => setOpen(false)); 28 | const [isGregoryCalendar, isGregoryCalendarToggle] = useToggle(false); 29 | const dateTimeUtils = new DateTimeUtils( 30 | isGregoryCalendar ? 'gregory' : 'jalali', 31 | isGregoryCalendar ? 'en' : 'fa' 32 | ); 33 | const [date, setDate] = useState(dateTimeUtils.getNow()); 34 | const [dateRange, setDateRange] = useState<[Dayjs] | [Dayjs, Dayjs] | []>( 35 | [] 36 | ); 37 | const [datesOfCurrentMonth, setDatesOfCurrentMonth] = useState< 38 | Dayjs[] | [] 39 | >([]); 40 | 41 | useEffect(() => { 42 | setDatesOfCurrentMonth(getAllDates(date)); 43 | }, [date]); 44 | 45 | useEffect(() => { 46 | if (dateRange.length) props.onChange?.(dateRange); 47 | }, [dateRange]); 48 | 49 | return ( 50 |
51 | undefined} 68 | onClick={toggle} 69 | readOnly 70 | /> 71 | 72 | 73 |
74 | 79 | 88 |
89 |
90 |
91 |
92 | ); 93 | }; 94 | 95 | export default Calendar; 96 | -------------------------------------------------------------------------------- /components/Inputs/Checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import Label from '../Label'; 2 | import Icon from "../../Icon"; 3 | import classes from './styles.module.scss'; 4 | import type { FC } from 'react'; 5 | import type { CheckboxProps } from './props'; 6 | 7 | const Checkbox: FC = ({ 8 | label, 9 | error, 10 | className, 11 | labelClassName, 12 | ...props 13 | }) => { 14 | return ( 15 |
16 |
43 | ); 44 | }; 45 | 46 | export default Checkbox; 47 | -------------------------------------------------------------------------------- /components/Inputs/Checkbox/props.ts: -------------------------------------------------------------------------------- 1 | import { InputsProps } from '../props'; 2 | 3 | export type CheckboxProps = InputsProps; 4 | -------------------------------------------------------------------------------- /components/Inputs/Checkbox/styles.module.scss: -------------------------------------------------------------------------------- 1 | .label { 2 | @apply inline-flex items-center gap-2 cursor-pointer; 3 | 4 | &:hover .checkbox .checkboxInput + .btn { 5 | @apply bg-grey-2; 6 | :global(.icon-checkmark) { 7 | @apply opacity-100; 8 | } 9 | } 10 | 11 | .checkbox { 12 | @apply relative inline-block w-5 h-5; 13 | 14 | & .checkboxInput { 15 | @apply hidden; 16 | 17 | &:checked + .btn { 18 | @apply bg-primary; 19 | 20 | :global(.icon-checkmark) { 21 | @apply opacity-100; 22 | } 23 | } 24 | 25 | &:disabled + .btn { 26 | @apply bg-grey-3; 27 | } 28 | } 29 | 30 | .btn { 31 | @apply flex justify-center items-center h-full absolute cursor-pointer inset-0 transition-all duration-200 bg-grey-1 32 | disabled:bg-grey-3 rounded; 33 | 34 | :global(.icon-checkmark) { 35 | @apply opacity-0; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /components/Inputs/Error/index.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../../Text'; 2 | import type { FC } from 'react'; 3 | import type { InputsProps } from '../props'; 4 | 5 | const Error: FC>> = ({ 6 | errorMessage, 7 | }) => ( 8 | 9 | {errorMessage} 10 | 11 | ); 12 | 13 | export default Error; 14 | -------------------------------------------------------------------------------- /components/Inputs/Input/index.tsx: -------------------------------------------------------------------------------- 1 | import Label from '../Label'; 2 | import Error from "../Error"; 3 | import Icon from "../../Icon"; 4 | import { twMerge } from 'tailwind-merge'; 5 | import classes from './styles.module.scss'; 6 | import type { FC } from 'react'; 7 | import type { InputProps } from './props'; 8 | 9 | const Input: FC = ({ 10 | label, 11 | iconRightName, 12 | iconLeftName, 13 | className, 14 | labelClassName, 15 | error, 16 | errorMessage, 17 | required, 18 | ...props 19 | }) => { 20 | return ( 21 |
22 |
59 | ); 60 | }; 61 | 62 | export default Input; 63 | -------------------------------------------------------------------------------- /components/Inputs/Input/props.ts: -------------------------------------------------------------------------------- 1 | import type {InputsProps} from '../props'; 2 | 3 | export interface InputProps extends InputsProps { 4 | iconRightName?: string; 5 | iconLeftName?: string; 6 | } 7 | -------------------------------------------------------------------------------- /components/Inputs/Input/styles.module.scss: -------------------------------------------------------------------------------- 1 | .label { 2 | @apply text-grey-4 w-full text-xs gap-2 flex flex-col; 3 | 4 | &:focus-within { 5 | @apply text-text; 6 | } 7 | 8 | /* &.readOnly { 9 | @apply text-grey-4; 10 | }*/ 11 | 12 | .input { 13 | @apply rounded-xl outline-0 w-full text-text border transition-all duration-200 border-grey-6 px-3 py-3 text-sm; 14 | 15 | &::placeholder { 16 | @apply text-grey-8; 17 | } 18 | 19 | &.hasIconRight { 20 | @apply pr-8; 21 | } 22 | 23 | &:hover { 24 | @apply border-grey-7; 25 | } 26 | 27 | &:focus { 28 | @apply border-text; 29 | } 30 | 31 | /* &:read-only { 32 | @apply bg-grey-6 border-grey-6 cursor-auto; 33 | }*/ 34 | 35 | &:disabled { 36 | @apply border-disabled bg-white; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /components/Inputs/Label/index.tsx: -------------------------------------------------------------------------------- 1 | import Text from '../../Text'; 2 | import { twMerge } from 'tailwind-merge'; 3 | import type { FC } from 'react'; 4 | import type { LabelProps } from './props'; 5 | 6 | const Index: FC = ({ error, label, labelClassName, required }) => { 7 | return ( 8 | 14 | {label || ''} 15 | {required && ( 16 | 20 | )} 21 | 22 | ); 23 | }; 24 | 25 | export default Index; 26 | -------------------------------------------------------------------------------- /components/Inputs/Label/props.ts: -------------------------------------------------------------------------------- 1 | import { InputsProps } from '../props'; 2 | 3 | export type LabelProps = Pick< 4 | InputsProps, 5 | 'label' | 'error' | 'labelClassName' | 'required' 6 | >; 7 | -------------------------------------------------------------------------------- /components/Inputs/Radio/index.tsx: -------------------------------------------------------------------------------- 1 | import Label from '../Label'; 2 | import classes from './styles.module.scss'; 3 | import type { FC } from 'react'; 4 | import type {RadioProps} from './props'; 5 | 6 | const Radio: FC = ({ 7 | className, 8 | labelClassName, 9 | label, 10 | error, 11 | ...props 12 | }) => { 13 | return ( 14 |
15 |