├── .eslintrc.cjs ├── .gitignore ├── .storybook ├── main.ts └── preview.ts ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public └── vite.svg ├── src ├── assets │ ├── react.svg │ └── vc-logo.png ├── components │ ├── avatar │ │ ├── avatar.stories.tsx │ │ ├── avatar.tsx │ │ ├── index.ts │ │ └── styled.ts │ ├── button │ │ ├── button.stories.tsx │ │ ├── button.tsx │ │ ├── index.ts │ │ └── styled.ts │ ├── card │ │ ├── card.stories.tsx │ │ ├── card.tsx │ │ ├── index.ts │ │ └── styled.ts │ ├── form-control │ │ ├── form-control.stories.tsx │ │ ├── form-control.tsx │ │ ├── index.ts │ │ └── styled.ts │ ├── header │ │ ├── header.stories.tsx │ │ ├── header.tsx │ │ ├── index.css │ │ └── index.ts │ ├── index.ts │ ├── link │ │ ├── index.ts │ │ ├── link.stories.tsx │ │ ├── link.tsx │ │ └── styled.ts │ ├── login │ │ ├── index.ts │ │ ├── login.stories.tsx │ │ ├── login.tsx │ │ ├── styled.ts │ │ └── use-login-logic.ts │ ├── modal │ │ ├── index.css │ │ ├── index.ts │ │ ├── modal.stories.tsx │ │ ├── modal.tsx │ │ └── styled.ts │ ├── search │ │ ├── index.ts │ │ ├── search.stories.tsx │ │ ├── search.tsx │ │ └── styled.ts │ ├── select │ │ ├── index.ts │ │ ├── select.stories.tsx │ │ ├── select.tsx │ │ ├── styled.ts │ │ └── use-select-logic.ts │ ├── skeleton │ │ ├── index.ts │ │ ├── skeleton.stories.tsx │ │ ├── skeleton.tsx │ │ └── styled.ts │ ├── spinner │ │ ├── index.ts │ │ ├── spinner.stories.tsx │ │ ├── spinner.tsx │ │ ├── styled.ts │ │ └── utils.ts │ ├── text-input │ │ ├── index.ts │ │ ├── styled.ts │ │ ├── text-input.stories.tsx │ │ └── text-input.tsx │ └── typography │ │ ├── index.ts │ │ ├── styled.ts │ │ ├── typography.stories.tsx │ │ └── typography.tsx ├── config │ ├── fonts │ │ ├── Poppins-Bold.ttf │ │ ├── Poppins-Light.ttf │ │ ├── Poppins-Medium.ttf │ │ ├── Poppins-Regular.ttf │ │ ├── Poppins-SemiBold.ttf │ │ ├── Poppins-Thin.ttf │ │ └── styles.css │ ├── global.styles.ts │ └── sizes.ts ├── icons │ ├── ArrowDown.tsx │ ├── Cross.tsx │ ├── Download.tsx │ ├── Search.tsx │ ├── User.tsx │ ├── arrow-down.svg │ ├── cross.svg │ ├── download.svg │ ├── index.ts │ ├── index.tsx │ ├── search.svg │ └── user.svg ├── index.tsx └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'], 5 | ignorePatterns: ['dist', '.eslintrc.cjs'], 6 | parser: '@typescript-eslint/parser', 7 | plugins: ['react-refresh'], 8 | rules: { 9 | 'react-refresh/only-export-components': [ 10 | 'warn', 11 | { allowConstantExport: true }, 12 | ], 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | lib 13 | storybook-static/ 14 | dist-ssr 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/react-vite"; 2 | 3 | const config: StorybookConfig = { 4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], 5 | addons: [ 6 | "@storybook/addon-onboarding", 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@chromatic-com/storybook", 10 | "@storybook/addon-interactions", 11 | ], 12 | framework: { 13 | name: "@storybook/react-vite", 14 | options: {}, 15 | }, 16 | docs: { 17 | autodocs: "tag", 18 | }, 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@storybook/react"; 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/i, 9 | }, 10 | }, 11 | }, 12 | }; 13 | 14 | export default preview; 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | VC logo 4 | 5 |

6 | 7 |

Vinyl Component Blocks ⚡️

8 |
9 | 10 |

11 | Bundle Size (Brotli) 12 | Version 13 | License 14 | Code Coverage 15 | 16 | Contribution Guidelines 17 | 18 | 19 | Website 20 | 21 | 22 |

23 | 24 |
25 | 26 |

27 | Vinyl Component Blocks is a UI component library designed to help you quickly build beautiful and functional user interfaces for your web applications. This library provides a collection of reusable and customizable React components that cover a wide range of UI elements. 28 |

29 | 30 |
31 | 32 | ## Introduction 33 | 34 | Offering a set of commonly used components that are designed to be easy to use, highly customizable, and visually appealing. Whether you're building a simple login form, a complex dashboard interface or seeking inspiration while building components in your development process, Vinyl Component(VC) Blocks has the basic components you need to create a polished user experience. 35 | 36 |
37 | 38 | ## Installation and Setup 🧱 39 | 40 | You can install Vinyl Component Blocks via npm or yarn: 41 | 42 | ```bash 43 | npm install vinyl-component-blocks 44 | ``` 45 | 46 | or 47 | 48 | ```bash 49 | yarn add vinyl-component-blocks 50 | ``` 51 | 52 | ## Usage 53 | 54 | Once installed, you can import the components you need from Vinyl Component Blocks and use them in your React application: 55 | 56 | ```jsx 57 | import React from "react"; 58 | import { 59 | Button, 60 | FormControl, 61 | Link, 62 | Search, 63 | Select, 64 | Skeleton, 65 | Spinner, 66 | TextInput, 67 | Typography, 68 | Avatar, 69 | Card, 70 | Modal, 71 | Header, 72 | } from "vinyl-component-blocks"; 73 | ``` 74 | 75 | ### Demo Example Combining Components 76 | 77 | ```jsx 78 | import React from "react"; 79 | import { Button, Card, Avatar } from "vinyl-component-blocks"; 80 | 81 | const DemoComponent = () => ( 82 |
83 | 84 | 85 |

John Doe

86 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

87 | 90 |
91 |
92 | ); 93 | 94 | export default DemoComponent; 95 | ``` 96 | 97 |
98 | 99 | - Providing a hands-on experience with the Vinyl Component Blocks library, showcasing the versatility and functionality of each component. Key components here include the `Header` and `Login` UI components. 100 | 101 |
102 | 103 | ```jsx 104 | import React from "react"; 105 | import { Button, Card, Avatar, Header, Login } from "vinyl-component-blocks"; 106 | 107 | const DemoComponent = () => ( 108 |
109 |
console.log("Logged out")} 112 | /> 113 | 114 | 115 |

John Doe

116 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

117 | 120 |
121 | console.log("Logged in")} 123 | onCreateAccount={() => console.log("Creating account")} 124 | /> 125 |
126 | ); 127 | 128 | export default DemoComponent; 129 | ``` 130 | 131 | ## Components 132 | 133 | ### Button 134 | 135 | | Prop | Type | Description | 136 | | --------- | ----------- | ----------------------------------------------------------- | 137 | | type | string | Button type (`default`, `danger`, `ghost`, `secondary` ) | 138 | | size | string | Button size (`default`, `large`, `small`) | 139 | | disabled | boolean | Disable button | 140 | | onClick | function | Click event handler | 141 | | children | ReactNode | Button content | 142 | | icon | ElementType | Icon component to display alongside the button text | 143 | | className | string | Additional CSS classes for styling | 144 | | loading | boolean | Specify whether the button should display a loading spinner | 145 | | href | string | URL for the button if it should act as a link | 146 | | as | ElementType | HTML element type for the button | 147 | | to | string | Destination path for the link if using the 'as' prop | 148 | | ...rest | any | Additional props for customization | 149 | 150 | Screenshot 2024-05-04 051425 151 | Screenshot 2024-05-04 051538 152 | 153 | ### FormControl 154 | 155 | | Prop | Type | Description | 156 | | ---------- | --------- | ---------------------------------- | 157 | | label | string | Label for the form control | 158 | | htmlFor | string | ID of the form control element | 159 | | error | string | Error message to display | 160 | | hint | string | Hint message to display | 161 | | disabled | boolean | Disable the form control | 162 | | className | string | Additional CSS classes for styling | 163 | | children | ReactNode | Form control element | 164 | | onBlur | function | Blur event handler | 165 | | onFocus | function | Focus event handler | 166 | | forceLabel | boolean | Force label to always show | 167 | | ...rest | any | Additional props for customization | 168 | 169 | Screenshot 2024-05-04 051640 170 | Screenshot 2024-05-04 051657 171 | 172 | ### Link 173 | 174 | | Prop | Type | Description | 175 | | --------- | ----------- | ---------------------------------- | 176 | | disabled | boolean | Disable the link | 177 | | className | string | Additional CSS classes for styling | 178 | | children | ReactNode | Link text | 179 | | as | ElementType | HTML element type (default is `a`) | 180 | | href | string | Link URL | 181 | | ...rest | any | Additional props for customization | 182 | 183 | Screenshot 2024-05-04 051835 184 | 185 | ### Search 186 | 187 | | Prop | Type | Description | 188 | | ----------- | ------------- | ------------------------------------------------------ | 189 | | className | string | Additional CSS classes for styling | 190 | | size | ComponentSize | Size of the search input (`default`, `small`, `large`) | 191 | | width | string | Width of the search input | 192 | | value | string | Current value of the search input | 193 | | onChange | function | Change event handler for the search input | 194 | | placeholder | string | Placeholder text for the search input | 195 | 196 | Screenshot 2024-05-04 052414 197 | 198 | ### Select 199 | 200 | | Prop | Type | Description | 201 | | ----------- | -------------- | ------------------------------------------------------ | 202 | | className | string | Additional CSS classes for styling | 203 | | size | ComponentSize | Size of the select input (`default`, `small`, `large`) | 204 | | disabled | boolean | Disable the select input | 205 | | error | boolean | Indicate error state | 206 | | width | string | Width of the select input | 207 | | option | SelectOption | Selected option | 208 | | listOptions | SelectOption[] | Array of options for the select input | 209 | | onChange | function | Change event handler for the select input | 210 | | placeholder | string | Placeholder text for the select input | 211 | 212 | Screenshot 2024-05-04 052459 213 | Screenshot 2024-05-04 051719 214 | Screenshot 2024-05-04 052626 215 | Screenshot 2024-05-04 052525 216 | 217 | ### Skeleton 218 | 219 | | Prop | Type | Description | 220 | | ------------ | ------ | ------------------------------------- | 221 | | width | number | Width of the skeleton element | 222 | | height | number | Height of the skeleton element | 223 | | className | string | Additional CSS classes for styling | 224 | | borderRadius | string | Border radius of the skeleton element | 225 | 226 | Screenshot 2024-05-04 052700 227 | 228 | ### Spinner 229 | 230 | | Prop | Type | Description | 231 | | --------- | ------- | ---------------------------------- | 232 | | size | number | Size of the spinner | 233 | | className | string | Additional CSS classes for styling | 234 | | light | boolean | Use light spinner variant | 235 | 236 | Screenshot 2024-05-04 052722 237 | 238 | ### TextInput 239 | 240 | | Prop | Type | Description | 241 | | ----------- | ------------- | ----------------------------------------------- | 242 | | icon | ElementType | Icon component to display | 243 | | size | ComponentSize | Size of the input (`default`, `small`, `large`) | 244 | | disabled | boolean | Disable the input | 245 | | error | boolean | Indicate error state | 246 | | value | string | Current value of the input | 247 | | onChange | function | Change event handler for the input | 248 | | placeholder | string | Placeholder text for the input | 249 | | width | string | Width of the input | 250 | | readonly | boolean | Make the input read-only | 251 | | clearable | boolean | Allow clearing the input | 252 | | ...rest | any | Additional props for customization | 253 | 254 | Screenshot 2024-05-04 052741 255 | Screenshot 2024-05-04 052835 256 | Screenshot 2024-05-04 052811 257 | 258 | ### Typography 259 | 260 | | Prop | Type | Description | 261 | | --------- | --------- | ------------------------------------------ | 262 | | variant | string | Typography variant (`h1`, `h2`, `h3`) | 263 | | align | string | Text alignment (`center`, `right`, `left`) | 264 | | className | string | Additional CSS classes for styling | 265 | | children | ReactNode | Text content | 266 | 267 | Screenshot 2024-05-04 052916 268 | 269 | ### Avatar 270 | 271 | | Prop | Type | Description | 272 | | -------- | ------ | ------------------------------- | 273 | | imageSrc | string | Source URL for the avatar image | 274 | 275 | Screenshot 2024-05-04 051356 276 | 277 | ### Card 278 | 279 | | Prop | Type | Description | 280 | | --------------- | --------- | ------------------------------------------ | 281 | | backgroundColor | string | Background color of the card | 282 | | children | ReactNode | Content to be displayed within the card | 283 | | padding | string | Padding around the content inside the card | 284 | 285 | Screenshot 2024-05-04 051605 286 | 287 | ### Modal 288 | 289 | | Prop | Type | Description | 290 | | -------- | --------- | ---------------------------------------- | 291 | | children | ReactNode | Content to be displayed within the modal | 292 | 293 | Screenshot 2024-05-04 052356 294 | 295 | ### Header 296 | 297 | | Prop | Type | Description | 298 | | --------------- | -------- | --------------------------------------- | 299 | | user | User | User object | 300 | | onLogin | function | Event handler for login action | 301 | | onLogout | function | Event handler for logout action | 302 | | onCreateAccount | function | Event handler for create account action | 303 | 304 | Screenshot 2024-05-04 051800 305 | Screenshot 2024-05-04 051817 306 | 307 | ### Login 308 | 309 | | Prop | Type | Description | 310 | | ------------- | -------- | ---------------------------------------- | 311 | | onSubmit | function | Event handler for form submission | 312 | | initialValues | object | Initial values for the form fields | 313 | | loading | boolean | Specify whether to display loading state | 314 | | registerLink | string | Link for user registration | 315 | Screenshot 2024-05-04 051925 316 | Screenshot 2024-05-04 051949 317 | 318 | 319 | 320 | ## External Resources and Dependencies 🌐 321 | 322 | The Vinyl Component Blocks library relies on the following external resources and dependencies: 323 | 324 | - **Styled Components**: Utilized for customizable styling of components. CSS-in-JS library. 325 | 326 | - **React**: Serves as the core of the Vinyl Component Blocks, providing the necessary infrastructure for building user interfaces in JavaScript. 327 | 328 | - **TypeScript**: Vinyl Component Blocks is super strictly typed. TypeScript as a strict syntactical superset of JavaScript adds static typing to the library. It enhances developer productivity and code quality. 329 | 330 | - **Email Validator**: Used for validating emails, for the input-component most specifically. Email Validator is a dependency that helps ensure that the entered email addresses conform to standard email format rules, enhancing the reliability and accuracy of user input validation. 331 | 332 | ## Demo Examples 🛠️ 333 | 334 | ### Customizing TextInput Component 335 | 336 | ```jsx 337 | import React from "react"; 338 | import { TextInput } from "vinyl-component-blocks"; 339 | 340 | const CustomTextInput = () => ( 341 | console.log(e.target.value)} 349 | placeholder="Enter text here" 350 | width="200px" 351 | readonly={false} 352 | clearable={true} 353 | /> 354 | ); 355 | 356 | export default CustomTextInput; 357 | ``` 358 | 359 | ### Demo Example, Simple Login UI 360 | 361 | ```jsx 362 | import React from "react"; 363 | import { Header, Login } from "vinyl-component-blocks"; 364 | 365 | const HeaderWithLogin = () => ( 366 |
367 |
console.log("Logged out")} 370 | /> 371 | console.log("Logged in")} 373 | onCreateAccount={() => console.log("Creating account")} 374 | /> 375 |
376 | ); 377 | 378 | export default HeaderWithLogin; 379 | ``` 380 | 381 | ### Customizing Components with Custom CSS Styles ✨ 382 | 383 | Customizing components with custom CSS styles allows you to tailor the appearance of UI components to match the specific design requirements of your project. Applying custom CSS classes to components, you can override default styles and apply unique visual enhancements. This approach offers flexibility and control over the presentation of components. 384 | 385 | #### Customizing Card Component 386 | 387 | ```jsx 388 | import React from "react"; 389 | import { Card } from "vinyl-component-blocks"; 390 | import "./custom-styles.css"; 391 | 392 | const CustomCard = () => ( 393 | 398 |

This is a Custom Card

399 |

400 | Stop reinventing the wheel! Our well-documented component library empowers 401 | you to concentrate on building unique features and functionalities. 402 |

403 |
404 | ); 405 | 406 | export default CustomCard; 407 | ``` 408 | 409 | #### Customizing TextInput Component 410 | 411 | ```jsx 412 | import React from "react"; 413 | import { TextInput } from "vinyl-component-blocks"; 414 | import "./custom-styles.css"; 415 | 416 | const CustomTextInput = () => ( 417 | console.log(e.target.value)} 425 | placeholder="Enter text here" 426 | width="200px" 427 | readonly={false} 428 | clearable={true} 429 | /> 430 | ); 431 | 432 | export default CustomTextInput; 433 | ``` 434 | 435 | In the `custom-styles.css` file: 436 | 437 | ```css 438 | /* custom-styles.css */ 439 | .custom-card { 440 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 441 | border-radius: 12px; 442 | } 443 | 444 | .custom-text-input { 445 | border: 2px solid #cccccc; 446 | border-radius: 8px; 447 | } 448 | ``` 449 | 450 | ## Contributing 451 | 452 | ### Adding New Components 453 | 454 | Contributions from developers who feel the need to expand the library with new components or features. Whether you've identified a potential component, have an idea for a new feature, or want to enhance existing functionality, your contributions are valuable to the community. 455 | 456 | If you find something that piques your interest or if the project has inspired you, feel free to contribute by submitting pull requests(PRs). Your contributions help improve the library for everyone. Don't forget to leave a star ⭐ on GitHub to show your support! 457 | 458 | ## Conclusion 459 | 460 | Vinyl Component Blocks offers a comprehensive collection of UI components that are easy to use and highly customizable. Say goodbye to design inconsistencies. 461 | 462 | So, stop reinventing the wheel! Our well-documented component library empowers you to create, clone and build unique component features and functionalities to your taste. 463 | 464 | Stop repeating work! Install, call, use, modify. 465 | `npm install vinyl-component-blocks`. 466 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Block Test 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vinyl-component-blocks", 3 | "version": "1.1.6", 4 | "main": "lib/index.js", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "compile-icons": "npx @svgr/cli -d src/icons src/icons --typescript", 9 | "copy-files": "copyfiles --up 1 src/config/fonts/* lib/", 10 | "build": "tsc && vite build", 11 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 12 | "preview": "vite preview", 13 | "storybook": "storybook dev -p 6006", 14 | "build-storybook": "storybook build", 15 | "prebuild": "npm run compile-icons && npm run copy-files", 16 | "prepublish": "npm run build" 17 | }, 18 | "dependencies": { 19 | "email-validator": "^2.0.4", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "styled-components": "^6.1.9" 23 | }, 24 | "devDependencies": { 25 | "@chromatic-com/storybook": "^1.3.3", 26 | "@storybook/addon-essentials": "^8.0.9", 27 | "@storybook/addon-interactions": "^8.0.9", 28 | "@storybook/addon-links": "^8.0.9", 29 | "@storybook/addon-onboarding": "^8.0.9", 30 | "@storybook/blocks": "^8.0.9", 31 | "@storybook/react": "^8.0.9", 32 | "@storybook/react-vite": "^8.0.9", 33 | "@storybook/test": "^8.0.9", 34 | "@svgr/cli": "^8.1.0", 35 | "@types/react": "^18.2.15", 36 | "@types/react-dom": "^18.2.7", 37 | "@types/styled-components": "^5.1.34", 38 | "@typescript-eslint/eslint-plugin": "^6.0.0", 39 | "@typescript-eslint/parser": "^6.0.0", 40 | "@vitejs/plugin-react": "^4.0.3", 41 | "copyfiles": "^2.4.1", 42 | "eslint": "^8.45.0", 43 | "eslint-plugin-react-hooks": "^4.6.0", 44 | "eslint-plugin-react-refresh": "^0.4.3", 45 | "eslint-plugin-storybook": "^0.8.0", 46 | "storybook": "^8.0.9", 47 | "typescript": "^5.0.2", 48 | "vite": "^4.4.5" 49 | }, 50 | "keywords": [ 51 | "UI", 52 | "components", 53 | "design-system", 54 | "auth-ui", 55 | "library", 56 | "accessibility", 57 | "open-source", 58 | "reusable-component", 59 | "design-engineering" 60 | ], 61 | "repository": { 62 | "type": "git", 63 | "url": "https://github.com/Vinyl-Davyl/vinyl-component-blocks" 64 | }, 65 | "author": { 66 | "email": "okononfuadavid@gmail.com", 67 | "name": "Vinyl-Davyl (Okononfua O David)", 68 | "url": "https://vinyldavyl.xyz" 69 | }, 70 | "license": "MIT", 71 | "description": "Modular, Reusable, and Styled UI Component Library. Stop repeating work, install, call, use, modify." 72 | } 73 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/vc-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vinyl-Davyl/vinyl-component-blocks/cdb592e13255fae06946ad1de1a5c4dcf1f59a12/src/assets/vc-logo.png -------------------------------------------------------------------------------- /src/components/avatar/avatar.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from "@storybook/react"; 2 | import Avatar from "."; 3 | 4 | const meta: Meta = { 5 | title: "Components/Avatar", 6 | component: Avatar, 7 | parameters: { 8 | layout: "centered", 9 | }, 10 | tags: ["autodocs"], 11 | }; 12 | 13 | // Export the metadata as default 14 | export default meta; 15 | 16 | type Story = StoryObj; 17 | 18 | export const Primary: Story = { 19 | args: { 20 | imageSrc: 21 | "https://miro.medium.com/v2/resize:fit:740/1*ooOH6jo8I0ns0J-BE0SAow.jpeg", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/avatar/avatar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AvatarImage } from "./styled"; 3 | 4 | export type AvatarProps = { 5 | imageSrc?: string; 6 | }; 7 | 8 | const Avatar: React.ForwardRefRenderFunction = ( 9 | props, 10 | ref 11 | ) => { 12 | const { imageSrc } = props; 13 | 14 | return ( 15 |
16 | 17 |
18 | ); 19 | }; 20 | 21 | export default React.forwardRef(Avatar); 22 | -------------------------------------------------------------------------------- /src/components/avatar/index.ts: -------------------------------------------------------------------------------- 1 | import Avatar from "./avatar"; 2 | 3 | export * from "./avatar"; 4 | 5 | export default Avatar; 6 | -------------------------------------------------------------------------------- /src/components/avatar/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | interface StyledAvatarProps { 4 | imageSrc?: string; 5 | } 6 | 7 | export const AvatarImage = styled.img` 8 | margin: 0px 20px 0px 0px; 9 | height: 40px; 10 | border-radius: 50%; 11 | `; 12 | -------------------------------------------------------------------------------- /src/components/button/button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Meta, StoryObj } from "@storybook/react"; 3 | 4 | import Button, { ButtonProps } from "."; 5 | import styled from "styled-components"; 6 | import DownloadIcon from "../../icons/Download"; 7 | 8 | const meta: Meta = { 9 | title: "Components/Button", 10 | component: Button, 11 | parameters: { 12 | layout: "centered", 13 | }, 14 | tags: ["autodocs"], 15 | }; 16 | 17 | export default meta; 18 | 19 | type Story = StoryObj; 20 | 21 | export const Default: Story = { 22 | args: { 23 | children: "Default Button", 24 | }, 25 | }; 26 | 27 | export const DangerButton: Story = { 28 | args: { 29 | children: "Danger Button", 30 | type: "danger", 31 | }, 32 | }; 33 | 34 | export const Ghost: Story = { 35 | args: { 36 | children: "Ghost Button", 37 | type: "ghost", 38 | }, 39 | }; 40 | 41 | export const Secondary: Story = { 42 | args: { 43 | children: "Secondary Button", 44 | type: "secondary", 45 | }, 46 | }; 47 | 48 | export const Disabled: Story = { 49 | args: { 50 | children: "Disabled Button", 51 | disabled: true, 52 | }, 53 | }; 54 | 55 | export const Loading: Story = { 56 | args: { 57 | children: "Button", 58 | loading: true, 59 | }, 60 | }; 61 | 62 | export const WithIcon: Story = { 63 | args: { 64 | icon: DownloadIcon, 65 | children: "Download", 66 | }, 67 | }; 68 | 69 | const ButtonRow = styled.div` 70 | display: flex; 71 | align-items: flex-start; 72 | margin-bottom: 10px; 73 | & > * { 74 | margin-right: 10px; 75 | } 76 | `; 77 | 78 | export const Sizes = () => { 79 | return ( 80 | <> 81 | 82 | 83 | 84 | 85 | 86 | 87 | 90 | 93 | 96 | 97 | 98 | 101 | 104 | 107 | 108 | 109 | 112 | 115 | 118 | 119 | 120 | 123 | 126 | 129 | 130 | 131 | ); 132 | }; 133 | -------------------------------------------------------------------------------- /src/components/button/button.tsx: -------------------------------------------------------------------------------- 1 | import React, { ElementType, MouseEventHandler, ReactNode } from 'react'; 2 | import { StyledButton, StyledIcon } from './styled'; 3 | import Spinner from '../spinner'; 4 | import { ComponentSize } from '../../config/sizes'; 5 | 6 | export type ButtonType = 'default' | 'danger' | 'ghost' | 'secondary'; 7 | 8 | interface BaseButtonProps { 9 | type?: ButtonType; 10 | icon?: ElementType; 11 | size?: ComponentSize; 12 | className?: string; 13 | children?: ReactNode; 14 | disabled?: boolean; 15 | loading?: boolean; 16 | } 17 | 18 | type HTMLButtonProps = { 19 | onClick?: MouseEventHandler; 20 | } & BaseButtonProps; 21 | 22 | /** 23 | * If href is supplied, button becomes an anchor link 24 | */ 25 | type HTMLAnchorProps = { 26 | href?: string; 27 | } & BaseButtonProps; 28 | 29 | /** 30 | * If `as` is supplied, button becomes a custom html node specified in `as` 31 | */ 32 | type CustomNodeProps = { 33 | as?: ElementType; 34 | to?: string; 35 | } & BaseButtonProps; 36 | 37 | export type ButtonProps = HTMLButtonProps & HTMLAnchorProps & CustomNodeProps; 38 | 39 | const Button: React.ForwardRefRenderFunction = (props, ref) => { 40 | const { 41 | type = 'default', 42 | icon, 43 | size = 'default', 44 | className, 45 | children, 46 | disabled = false, 47 | loading, 48 | onClick, 49 | href, 50 | as, 51 | to, 52 | } = props; 53 | 54 | const styles = { 55 | innerType: type, 56 | size, 57 | disabled, 58 | withText: children != null 59 | } 60 | 61 | const spinnerStyles = { 62 | size: size === 'large' ? 25 : size === 'default' ? 20 : 15, 63 | light: true, 64 | } 65 | 66 | const childrenWithIcon = !icon ? children : ( 67 | <> 68 | {children} 69 | 70 | 71 | ); 72 | 73 | if (as && !disabled) { 74 | return ( 75 | 82 | {loading ? ( 83 | <> 84 | Loading 85 | 86 | 87 | ) : childrenWithIcon} 88 | 89 | ) 90 | } 91 | 92 | if (href && !disabled) { 93 | return ( 94 | } 98 | className={className} 99 | {...styles} 100 | > 101 | {loading ? ( 102 | <> 103 | Loading 104 | 105 | 106 | ) : childrenWithIcon} 107 | 108 | ); 109 | } 110 | 111 | return ( 112 | } 117 | className={className} 118 | {...styles} 119 | > 120 | {loading ? ( 121 | <> 122 | Loading 123 | 124 | 125 | ) : childrenWithIcon } 126 | 127 | ); 128 | } 129 | 130 | export default React.forwardRef(Button); -------------------------------------------------------------------------------- /src/components/button/index.ts: -------------------------------------------------------------------------------- 1 | import Button from './button'; 2 | 3 | export * from './button'; 4 | 5 | export default Button; -------------------------------------------------------------------------------- /src/components/button/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { ComponentSize, heights, sidePaddings } from "../../config/sizes"; 3 | import { ButtonType } from "./button"; 4 | 5 | type StateColors = { 6 | regular: string; 7 | hover: string; 8 | }; 9 | 10 | const typeColors: { [key in ButtonType]: StateColors } = { 11 | default: { 12 | regular: "#1ea7fd", 13 | hover: "#2e27cc", 14 | }, 15 | danger: { 16 | regular: "#d93848", 17 | hover: "#eb4d5d", 18 | }, 19 | ghost: { 20 | regular: "transparent", 21 | hover: "#dbdbdb", 22 | }, 23 | secondary: { 24 | regular: "#000", 25 | hover: "#3d3d3d", 26 | }, 27 | }; 28 | 29 | interface StyledButtonProps { 30 | innerType: ButtonType; 31 | size: ComponentSize; 32 | withText: boolean; 33 | } 34 | 35 | /* Real tag is assigned dynamically */ 36 | export const StyledButton = styled.button` 37 | display: flex; 38 | align-items: center; 39 | justify-content: center; 40 | 41 | /* Add margin in case of loading or icon */ 42 | & > *:nth-child(1) { 43 | margin-left: ${(pr) => (pr.withText ? 7 : 0)}px; 44 | } 45 | font-size: 15px; 46 | 47 | border: none; 48 | border-radius: 3em; 49 | cursor: pointer; 50 | background-color: ${(pr) => typeColors[pr.innerType].regular}; 51 | padding: 0 ${(pr) => sidePaddings[pr.size]}px; 52 | height: ${(pr) => heights[pr.size]}px; 53 | color: ${(pr) => 54 | pr.innerType === "ghost" ? typeColors["default"].regular : "#fff"}; 55 | ${(pr) => 56 | pr.disabled 57 | ? ` 58 | background-color: #a6a6a6; 59 | color: #5e5e5e; 60 | cursor: not-allowed; 61 | 62 | &:hover { 63 | background-color: #a6a6a6 !important; 64 | color: #5e5e5e !important; 65 | } 66 | ` 67 | : ""} 68 | outline: none; 69 | 70 | &:focus { 71 | box-shadow: 72 | 0 0 0 1px #fff, 73 | 0 0 0 2px ${(pr) => typeColors[pr.innerType].regular}; 74 | } 75 | &:hover { 76 | background-color: ${(pr) => typeColors[pr.innerType].hover}; 77 | } 78 | `; 79 | 80 | export const StyledIcon = styled.div` 81 | height: 20px; 82 | `; 83 | -------------------------------------------------------------------------------- /src/components/card/card.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from "@storybook/react"; 2 | import React from "react"; 3 | import Card from "./index"; 4 | 5 | const meta: Meta = { 6 | title: "Components/Card", 7 | component: Card, 8 | parameters: { 9 | layout: "centered", 10 | }, 11 | tags: ["autodocs"], 12 | }; 13 | 14 | // Export the metadata as default 15 | export default meta; 16 | 17 | type Story = StoryObj; 18 | 19 | export const Default: Story = { 20 | // Normal.args = {children: