diff --git a/package-lock.json b/package-lock.json index 3eec3ee..ab81a5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "fs": "^0.0.1-security", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router": "^7.7.1" }, "devDependencies": { @@ -3512,6 +3513,15 @@ "react": "^19.1.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index a874ef1..7365886 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "fs": "^0.0.1-security", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router": "^7.7.1" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index fe3e326..6d97159 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,15 @@ - import './App.css' import { Typography } from './core/typography' function App() { - - return ( <> - + Welcome to React Vite Template This is a simple template to get you started with React and Vite. - ) } diff --git a/src/components/common/atoms/Fab/Fab.tsx b/src/components/common/atoms/Fab/Fab.tsx index e8adb8b..6839f8d 100644 --- a/src/components/common/atoms/Fab/Fab.tsx +++ b/src/components/common/atoms/Fab/Fab.tsx @@ -1,15 +1,48 @@ -import type { FC } from "react"; -import * as S from "./styled"; +import * as S from './styled' +import { MdAdd } from 'react-icons/md' -export type FabColor = "primary" | "secondary" | "danger" | "success" | "info"; +export type FabBgColor = + | 'primary' + | 'secondary' + | 'danger' + | 'success' + | 'warning' + | 'info' +export type FabTextColor = 'primary' | 'secondary' +export type Fabsize = 'sm' | 'md' | 'lg' +export type FabPlacement = + | 'bottom-right' + | 'bottom-left' + | 'top-right' + | 'top-left' type Props = { - onClick: () => void; - color?: FabColor; -}; + onClick: () => void + bgColor?: FabBgColor + textColor?: FabTextColor + size?: Fabsize + placement?: FabPlacement +} -const Fab: FC = ({ onClick, color = "primary" }) => { - return ; -}; +function Fab({ + onClick, + bgColor = 'primary', + textColor = 'secondary', + size = 'md', + placement = 'top-left', +}: Props) { + return ( + + + + ) +} -export default Fab; +export default Fab diff --git a/src/components/common/atoms/Fab/index.ts b/src/components/common/atoms/Fab/index.ts index e69de29..a1e7b23 100644 --- a/src/components/common/atoms/Fab/index.ts +++ b/src/components/common/atoms/Fab/index.ts @@ -0,0 +1,3 @@ +import Fab from './Fab' + +export default Fab diff --git a/src/components/common/atoms/Fab/styled.ts b/src/components/common/atoms/Fab/styled.ts index 64778e6..802e420 100644 --- a/src/components/common/atoms/Fab/styled.ts +++ b/src/components/common/atoms/Fab/styled.ts @@ -1,17 +1,44 @@ import styled from '@emotion/styled' +import type { FabBgColor, Fabsize, FabPlacement } from './Fab' +import type { FabTextColor } from './Fab' +import tokens from '../../../../core/tokens' export const Container = styled.div<{ - position?: 'absolute' | 'fixed' | 'relative' - bottom?: number - right?: number - top?: number - left?: number - zIndex?: number -}>` - position: ${({ position }) => position ?? 'fixed'}; - bottom: ${({ bottom }) => bottom ?? 20}px; - right: ${({ right }) => right ?? 20}px; - top: ${({ top }) => top ?? 'auto'}; - left: ${({ left }) => left ?? 'auto'}; - z-index: ${({ zIndex }) => zIndex ?? 1000}; -`; + $bgColor: FabBgColor + $textColor: FabTextColor + $size: Fabsize + $placement: FabPlacement +}>(({ theme, $bgColor, $textColor, $size, $placement }) => { + const pos = $placement || 'bottom-right' + return { + position: 'fixed', + ...(pos.includes('top') ? { top: 16 } : { bottom: 16 }), + ...(pos.includes('right') ? { right: 16 } : { left: 16 }), + + backgroundColor: theme.colors[$bgColor], + color: theme.colors.text[$textColor], + width: tokens.fabSize[$size], + height: tokens.fabSize[$size], + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + border: 'none', + borderRadius: tokens.borderRadius.ROUND, + cursor: 'pointer', + boxShadow: theme.colors.boxShadow, + padding: tokens.padding.ZERO, + fontWeight: tokens.text.fontWeight.semiBold, + lineHeight: 0, + zIndex: 1000, + + svg: { + width: tokens.size.LARGE, + height: tokens.size.LARGE, + }, + + '&:focus, &:focus-visible': { + outline: 'none !important', + boxShadow: 'none !important', + }, + } +}) diff --git a/src/core/theme.ts b/src/core/theme.ts index 82042e1..e1f49d6 100644 --- a/src/core/theme.ts +++ b/src/core/theme.ts @@ -1,184 +1,187 @@ const neutrals = { - black: '#0F0F0F', - blackOpacity: '#00000010', - grayOpacity: '#D1D5DBB3', - white: '#FFFFFF', - whiteOpacity: '#FFFFFFE6', + black: '#0F0F0F', + blackOpacity: '#00000010', + grayOpacity: '#D1D5DBB3', + white: '#FFFFFF', + whiteOpacity: '#FFFFFFE6', } const brandColors = { - primary: '#', - secondary: '#', - tertiary: '#' + primary: '#f8485e', + secondary: '#3c3c3a', + tertiary: '#fca2ae', } const background = { - primaryGrey: '#EFEFE9', - secondaryGrey: '#F5F5F5', - tertiaryGrey: '#CCCCCC', + primaryGrey: '#EFEFE9', + secondaryGrey: '#F5F5F5', + tertiaryGrey: '#CCCCCC', } const systemColors = { - error: '#', - danger: '#', - success: '#', - warning: '#', + error: '#D32F2F', + danger: '#C62828', + success: '#2E7D32', + warning: '#ED6C02', + info: '#0288D1', } const textColors = { - primary: '#000000E5', - secondary: '#0000009E', - disabled: '#0000006D', - iconColor: '#3C4A63', - black: neutrals.black, + primary: '#000000E5', + secondary: '#FFFFFF', + disabled: '#0000006D', + iconColor: '#3C4A63', + black: neutrals.black, + white: neutrals.white, } const hover = { - primaryGrey: '#EFEFE966', + primaryGrey: '#EFEFE966', } - const brandPalette = { - // Primary - primary: brandColors.primary, - secondary: brandColors.secondary, - tertiary: brandColors.tertiary, - - // Background - primaryBackground: background.primaryGrey, - secondaryBackground: background.secondaryGrey, - tertiaryBackground: background.tertiaryGrey, - - // System colors - error: systemColors.error, - danger: systemColors.danger, - success: systemColors.success, - warning: systemColors.warning, - - // Neutrals - black: neutrals.black, - white: neutrals.white, - boxShadow: neutrals.blackOpacity, - scrollBar: neutrals.grayOpacity, - - // Text - text: { - primary: textColors.primary, - secondary: textColors.secondary, - disabled: textColors.disabled, - iconColor: textColors.iconColor, - black: textColors.black, - }, - - - // Hover - hoverPrimaryBackground: hover.primaryGrey, - hoverIconDisabled: background.tertiaryGrey, - - // Buttons - primaryButton: brandColors.primary, + // Primary + primary: brandColors.primary, + secondary: brandColors.secondary, + tertiary: brandColors.tertiary, + + // Background + primaryBackground: background.primaryGrey, + secondaryBackground: background.secondaryGrey, + tertiaryBackground: background.tertiaryGrey, + + // System colors + error: systemColors.error, + danger: systemColors.danger, + success: systemColors.success, + warning: systemColors.warning, + info: systemColors.info, + + // Neutrals + black: neutrals.black, + white: neutrals.white, + boxShadow: neutrals.blackOpacity, + scrollBar: neutrals.grayOpacity, + + // Text + text: { + primary: textColors.primary, + secondary: textColors.secondary, + disabled: textColors.disabled, + iconColor: textColors.iconColor, + black: textColors.black, + white: textColors.white, + }, + + // Hover + hoverPrimaryBackground: hover.primaryGrey, + hoverIconDisabled: background.tertiaryGrey, + + // Buttons + primaryButton: brandColors.primary, } type ButtonVariantStyle = { - background: string - text: string - hover: string - border: string + background: string + text: string + hover: string + border: string } type ButtonStyles = { - - [key: string]: { - filled: ButtonVariantStyle - outlined: ButtonVariantStyle - } + [key: string]: { + filled: ButtonVariantStyle + outlined: ButtonVariantStyle + } } type ThemeConfig = { - colors: { - primary: string - secondary: string - tertiary: string + colors: { + primary: string + secondary: string + tertiary: string - primaryBackground: string - secondaryBackground: string - tertiaryBackground: string + primaryBackground: string + secondaryBackground: string + tertiaryBackground: string - error: string - danger: string - success: string - warning: string + error: string + danger: string + success: string + warning: string + info: string - black: string - white: string + black: string + white: string - boxShadow: string - scrollBar: string + boxShadow: string + scrollBar: string - text: { - primary: string - secondary: string - disabled: string - iconColor: string - } - - } - typography: { - fontFamily?: string - fontSize?: number + text: { + primary: string + secondary: string + disabled: string + iconColor: string + white: string } - buttonStyles: ButtonStyles + } + typography: { + fontFamily?: string + fontSize?: number + } + buttonStyles: ButtonStyles } const light: ThemeConfig = { - colors: { - ...brandPalette, - + colors: { + ...brandPalette, + }, + typography: { + fontFamily: 'Inter, sans-serif', + fontSize: 16, + }, + buttonStyles: { + primary: { + filled: { + background: brandPalette.primaryButton, + text: brandPalette.white, + border: 'none', + hover: brandPalette.primaryButton, + }, + outlined: { + background: 'transparent', + text: brandPalette.primaryButton, + border: `inset 0 0 0 1px ${brandPalette.primaryButton}`, + hover: brandPalette.primaryButton, + }, }, - typography: { - fontFamily: 'Inter, sans-serif', - fontSize: 16, - }, - buttonStyles: { - primary: { - filled: { - background: brandPalette.primaryButton, - text: brandPalette.white, - border: 'none', - hover: brandPalette.primaryButton, - }, - outlined: { - background: 'transparent', - text: brandPalette.primaryButton, - border: `inset 0 0 0 1px ${brandPalette.primaryButton}`, - hover: brandPalette.primaryButton, - }, - }, - danger: { - filled: { - text: brandPalette.white, - border: 'none', - background: brandPalette.danger, - hover: brandPalette.danger, - }, - outlined: { - background: 'transparent', - text: brandPalette.danger, - border: `inset 0 0 0 1px ${brandPalette.danger}`, - hover: brandPalette.danger, - }, - }, + danger: { + filled: { + text: brandPalette.white, + border: 'none', + background: brandPalette.danger, + hover: brandPalette.danger, + }, + outlined: { + background: 'transparent', + text: brandPalette.danger, + border: `inset 0 0 0 1px ${brandPalette.danger}`, + hover: brandPalette.danger, + }, }, + }, } export const themes = { - light, + light, } declare module '@emotion/react' { - export interface Theme { - colors: ThemeConfig['colors'] - typography: ThemeConfig['typography'] - buttonStyles: ThemeConfig['buttonStyles'] - } -} \ No newline at end of file + export interface Theme { + colors: ThemeConfig['colors'] + typography: ThemeConfig['typography'] + buttonStyles: ThemeConfig['buttonStyles'] + } +} + +export default themes.light diff --git a/src/core/tokens.ts b/src/core/tokens.ts index 6c976f7..6f20ac3 100644 --- a/src/core/tokens.ts +++ b/src/core/tokens.ts @@ -1,78 +1,85 @@ // https://dev.to/gerryleonugroho/responsive-design-breakpoints-2025-playbook-53ih#:~:text=Breakpoints%20are%20specific%20screen%20widths,keeping%20users%20engaged%20and%20satisfied. const breakpoints = { - laptop: '1600px', - tablet: '911px', - tabletLarge: '1025px', - phone: '480px', + laptop: '1600px', + tablet: '911px', + tabletLarge: '1025px', + phone: '480px', } const text = { - lineHeight: 6, - fontSize: { - SMALL: 14, - BASELINE: 16, - MEDIUM: 20, - LARGE: 24, - HEADING_SMALL: 32, - HEADING_MEDIUM: 48, - HEADING_LARGE: 64, - }, - fontWeight: { - light: 300, - normal: 400, - medium: 500, - semiBold: 600, - bold: 700, - }, + lineHeight: 6, + fontSize: { + SMALL: 14, + BASELINE: 16, + MEDIUM: 20, + LARGE: 24, + HEADING_SMALL: 32, + HEADING_MEDIUM: 48, + HEADING_LARGE: 64, + }, + fontWeight: { + light: 300, + normal: 400, + medium: 500, + semiBold: 600, + bold: 700, + }, } const padding = { - ZERO: 0, - BASELINE: 8, - CONTAINER: 40, + ZERO: 0, + BASELINE: 8, + CONTAINER: 40, } const borderRadius = { - ZERO: 0, - BASELINE: 8, - SMALL: 16, - MEDIUM: 24, - LARGE: 32, - ROUND: 50, + ZERO: 0, + BASELINE: 8, + SMALL: 16, + MEDIUM: 24, + LARGE: 32, + ROUND: 50, } const margin = { - ZERO: 0, - BASELINE: 8, + ZERO: 0, + BASELINE: 8, } const spacing = { - ZERO: 0, - BASELINE: 8, + ZERO: 0, + BASELINE: 8, } const gap = { - BASELINE: 8, - LARGE: 16, + BASELINE: 8, + LARGE: 16, } const size = { - BASELINE: 8, - SMALL: 16, - MEDIUM: 24, - LARGE: 32, + BASELINE: 8, + SMALL: 16, + MEDIUM: 24, + LARGE: 32, +} + +const fabSize = { + sm: 40, + md: 56, + lg: 72, } const tokens = { - breakpoints, - gap, - padding, - margin, - spacing, - text, - borderRadius, - size, + breakpoints, + gap, + padding, + margin, + spacing, + text, + borderRadius, + size, + fabSize, } export default tokens diff --git a/src/main.tsx b/src/main.tsx index c353f6f..17f8167 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,17 +2,20 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' -import { BrowserRouter, Route, Routes } from "react-router"; - +import { BrowserRouter, Route, Routes } from 'react-router' +import { ThemeProvider } from '@emotion/react' +import theme from './core/theme.ts' createRoot(document.getElementById('root')!).render( - - - //TODO: Add more routes that takes you to different template pages - } /> - About Page} /> - - - , + + + + {/* TODO: Add more routes that takes you to different template pages */} + } /> + About Page} /> + + + + )