├── .npmrc ├── components ├── NavCard │ ├── index.js │ ├── NavCard.stories.jsx │ ├── NavCard.test.jsx │ └── NavCard.jsx ├── CodeBlock │ ├── index.js │ ├── CodeBlock.test.jsx │ ├── CodeBlock.stories.jsx │ └── CodeBlock.jsx ├── BrandImage │ ├── index.js │ ├── BrandImage.test.jsx │ ├── BrandImage.stories.jsx │ ├── __snapshots__ │ │ └── BrandImage.test.jsx.snap │ └── BrandImage.jsx └── Introduction.mdx ├── jsconfig.json ├── public ├── favicon.ico ├── vercel.svg ├── stories │ └── assets │ │ ├── direction.svg │ │ ├── flow.svg │ │ ├── code-brackets.svg │ │ ├── comments.svg │ │ ├── repo.svg │ │ ├── plugin.svg │ │ ├── stackalt.svg │ │ └── colors.svg └── next.svg ├── postcss.config.js ├── next.config.js ├── .storybook ├── manager.js ├── manager-head.html ├── appTheme.js ├── preview.js └── main.js ├── pages ├── api │ └── hello.js ├── _document.js ├── _app.js └── index.js ├── jest.setup.js ├── .eslintrc.json ├── styles └── globals.css ├── .gitignore ├── .github └── workflows │ └── tests.yml ├── tailwind.config.js ├── .prettierrc.json ├── jest.config.js ├── PULL_REQUEST_TEMPLATE.md ├── LICENSE ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /components/NavCard/index.js: -------------------------------------------------------------------------------- 1 | import NavCard from './NavCard'; 2 | 3 | export default NavCard; 4 | -------------------------------------------------------------------------------- /components/CodeBlock/index.js: -------------------------------------------------------------------------------- 1 | import CodeBlock from './CodeBlock'; 2 | 3 | export default CodeBlock; 4 | -------------------------------------------------------------------------------- /components/BrandImage/index.js: -------------------------------------------------------------------------------- 1 | import BrandImage from './BrandImage'; 2 | 3 | export default BrandImage; 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Showrin/nextjs-tailwind-storybook-with-ci-cd/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | }; 5 | 6 | module.exports = nextConfig; 7 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/manager-api'; 2 | import appTheme from './appTheme'; 3 | 4 | addons.setConfig({ 5 | theme: appTheme, 6 | }); 7 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }); 5 | } 6 | -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // Optional: configure or set up a testing framework before each test. 2 | // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` 3 | 4 | // Used for __tests__/testing-library.js 5 | // Learn more: https://github.com/testing-library/jest-dom 6 | import '@testing-library/jest-dom/extend-expect'; 7 | -------------------------------------------------------------------------------- /components/BrandImage/BrandImage.test.jsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import BrandImage from './BrandImage'; 3 | import '@testing-library/jest-dom'; 4 | 5 | describe('Home', () => { 6 | it('renders BrandImage unchanged', () => { 7 | const { container } = render(); 8 | 9 | expect(container).toMatchSnapshot(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /.storybook/appTheme.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming/create'; 2 | 3 | // See more on https://storybook.js.org/docs/ember/configure/theming 4 | export default create({ 5 | base: 'light', 6 | brandTitle: 'Design Book', 7 | brandUrl: 'https://www.showrin.com/', 8 | brandImage: 'https://storybook.js.org/images/placeholders/350x150.png', 9 | brandTarget: '_self', 10 | }); 11 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | import '@/styles/globals.css'; 4 | 5 | export default function App({ Component, pageProps }) { 6 | return ; 7 | } 8 | 9 | App.defaultProps = { 10 | Component: null, 11 | pageProps: {}, 12 | }; 13 | 14 | App.propTypes = { 15 | Component: PropTypes.func, 16 | pageProps: PropTypes.shape({}), 17 | }; 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier", "plugin:storybook/recommended"], 3 | "rules": { 4 | "no-unused-vars": [ 5 | "error", 6 | { 7 | "varsIgnorePattern": "React" 8 | } 9 | ], 10 | "react/prop-types": [2], 11 | "react/require-default-props": [ 12 | 2, 13 | { 14 | "forbidDefaultForRequired": true, 15 | "functions": "defaultProps" 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/BrandImage/BrandImage.stories.jsx: -------------------------------------------------------------------------------- 1 | import BrandImage from './BrandImage'; 2 | 3 | // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction 4 | export default { 5 | title: 'Components/BrandImage', 6 | component: BrandImage, 7 | tags: ['autodocs'], 8 | }; 9 | 10 | // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args 11 | export const BasicBrandImage = { 12 | args: {}, 13 | }; 14 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | } 22 | -------------------------------------------------------------------------------- /components/CodeBlock/CodeBlock.test.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import CodeBlock from './CodeBlock'; 3 | import '@testing-library/jest-dom'; 4 | 5 | describe('CodeBlock', () => { 6 | const children = 'This is the child'; 7 | 8 | it('renders a codeblock with provided children', () => { 9 | render({children}); 10 | 11 | const element = screen.getByText(children); 12 | 13 | expect(element).toBeInTheDocument(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react').Preview } */ 2 | import * as nextImage from 'next/image'; 3 | 4 | import '../styles/globals.css'; 5 | 6 | Object.defineProperty(nextImage, 'default', { 7 | configurable: true, 8 | value: (props) => , 9 | }); 10 | 11 | export const parameters = { 12 | actions: { argTypesRegex: '^on[A-Z].*' }, 13 | controls: { 14 | matchers: { 15 | color: /(background|color)$/i, 16 | date: /Date$/, 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /.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 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /components/CodeBlock/CodeBlock.stories.jsx: -------------------------------------------------------------------------------- 1 | import CodeBlock from './CodeBlock'; 2 | 3 | // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction 4 | export default { 5 | title: 'Components/CodeBlock', 6 | component: CodeBlock, 7 | tags: ['autodocs'], 8 | }; 9 | 10 | // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args 11 | export const BasicCodeBlock = { 12 | args: { 13 | children: "I'm the only child of this code block", 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests CI 2 | 3 | on: push 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Test using Node.js 11 | uses: actions/setup-node@v1 12 | with: 13 | node-version: '16.10.0' 14 | - name: Install yarn 15 | run: npm install -g yarn 16 | - name: Install Dependencies 17 | run: yarn install 18 | - name: Run JS Linter 19 | run: yarn lint 20 | - name: Run JS Tests 21 | run: yarn test 22 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx}', 5 | './components/**/*.{js,ts,jsx,tsx}', 6 | './app/**/*.{js,ts,jsx,tsx}', 7 | './stories/**/*.{js,ts,jsx,tsx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | }; 19 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "semi": true, 6 | "useTabs": false, 7 | "tabWidth": 2, 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "endOfLine": "lf", 11 | "overrides": [ 12 | { 13 | "files": "*.scss", 14 | "options": { 15 | "singleQuote": false 16 | } 17 | }, 18 | { 19 | "files": "*.html", 20 | "options": { 21 | "singleQuote": false 22 | } 23 | } 24 | ], 25 | "plugins": ["prettier-plugin-tailwindcss"] 26 | } 27 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/NavCard/NavCard.stories.jsx: -------------------------------------------------------------------------------- 1 | import NavCard from './NavCard'; 2 | 3 | // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction 4 | export default { 5 | title: 'Components/NavCard', 6 | component: NavCard, 7 | tags: ['autodocs'], 8 | }; 9 | 10 | // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args 11 | export const BasicNavCard = { 12 | args: { 13 | title: 'Hi There', 14 | description: "I'm Showrin. A self taught engineer. Working on building things for web.", 15 | href: 'https://www.showrin.com/', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /components/CodeBlock/CodeBlock.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | /** 5 | * > It will show a code block with a code snippet. Great for presenting code snippets. 6 | */ 7 | 8 | const CodeBlock = ({ children }) => { 9 | return ( 10 |
11 |       {children}
12 |     
13 | ); 14 | }; 15 | 16 | CodeBlock.propTypes = { 17 | /** 18 | * The code snippet that needs to be shown. 19 | */ 20 | children: PropTypes.node.isRequired, 21 | }; 22 | 23 | export default CodeBlock; 24 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const nextJest = require('next/jest'); 2 | 3 | const createJestConfig = nextJest({ 4 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 5 | dir: './', 6 | }); 7 | 8 | // Add any custom config to be passed to Jest 9 | /** @type {import('jest').Config} */ 10 | const customJestConfig = { 11 | // Add more setup options before each test is run 12 | testMatch: ['**/components/**/*.test.[jt]s?(x)'], 13 | setupFilesAfterEnv: ['/jest.setup.js'], 14 | testEnvironment: 'jest-environment-jsdom', 15 | }; 16 | 17 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async 18 | module.exports = createJestConfig(customJestConfig); 19 | -------------------------------------------------------------------------------- /components/NavCard/NavCard.test.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import NavCard from './NavCard'; 3 | import '@testing-library/jest-dom'; 4 | 5 | describe('NavCard', () => { 6 | const props = { 7 | title: 'Docs', 8 | description: 'Find in-depth information about Next.js features and API.', 9 | href: 'https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app', 10 | }; 11 | 12 | it('renders a NavCard', () => { 13 | render(); 14 | 15 | const linkElement = screen.getByRole("link"); 16 | const titleText = screen.getByText(props.title); 17 | const descriptionText = screen.getByText(props.description); 18 | 19 | expect(linkElement).toHaveAttribute('href', props.href); 20 | expect(titleText).toBeInTheDocument(); 21 | expect(descriptionText).toBeInTheDocument(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue Number 2 | N/A 3 | 4 | # Description 5 | 6 | 7 | # Proposed Changes 8 | 9 | 10 | 11 | # Checklist 12 | - [ ] The code follows the project's style guidelines and conventions. 13 | - [ ] All existing tests pass successfully. 14 | - [ ] New tests have been added to cover the changes or improvements. 15 | - [ ] Documentation has been updated or created to reflect the changes made. 16 | - [ ] The commit messages are clear and descriptive. 17 | - [ ] The pull request title concisely summarizes the changes. 18 | 19 | # Screenshots (if Any) 20 | 21 | -------------------------------------------------------------------------------- /components/BrandImage/__snapshots__/BrandImage.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Home renders BrandImage unchanged 1`] = ` 4 |
5 |
8 | Next.js Logo 19 |
20 |
21 | `; 22 | -------------------------------------------------------------------------------- /components/BrandImage/BrandImage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | 4 | /** 5 | * > It will show the brand image of the site with some styles. 6 | */ 7 | 8 | const BrandImage = () => { 9 | return ( 10 |
11 | Next.js Logo 19 |
20 | ); 21 | }; 22 | 23 | export default BrandImage; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Showrin Barua 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 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/nextjs').StorybookConfig } */ 2 | const path = require('path'); 3 | 4 | const config = { 5 | stories: ['../components/**/*.mdx', '../components/**/*.stories.@(js|jsx|ts|tsx)'], 6 | staticDirs: ['../public'], 7 | addons: [ 8 | '@storybook/addon-links', 9 | '@storybook/addon-essentials', 10 | '@storybook/addon-interactions', 11 | ], 12 | core: { 13 | builder: 'webpack5', 14 | }, 15 | framework: { 16 | name: '@storybook/nextjs', 17 | options: {}, 18 | }, 19 | docs: { 20 | autodocs: 'tag', 21 | }, 22 | webpackFinal: async (config) => { 23 | config.module.rules.push({ 24 | test: /\.scss$/, 25 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'], 26 | }); 27 | 28 | config.resolve.alias = { 29 | ...config.resolve?.alias, 30 | '@': [path.resolve(__dirname, '../')], 31 | }; 32 | 33 | /** 34 | * Fixes font import with / 35 | * @see https://github.com/storybookjs/storybook/issues/12844#issuecomment-867544160 36 | */ 37 | config.resolve.roots = [path.resolve(__dirname, '../public'), 'node_modules']; 38 | 39 | return config; 40 | }, 41 | }; 42 | 43 | export default config; 44 | -------------------------------------------------------------------------------- /public/stories/assets/direction.svg: -------------------------------------------------------------------------------- 1 | illustration/direction -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/stories/assets/flow.svg: -------------------------------------------------------------------------------- 1 | illustration/flow -------------------------------------------------------------------------------- /public/stories/assets/code-brackets.svg: -------------------------------------------------------------------------------- 1 | illustration/code-brackets -------------------------------------------------------------------------------- /public/stories/assets/comments.svg: -------------------------------------------------------------------------------- 1 | illustration/comments -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-tailwind-storybook-with-ci-cd", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest", 11 | "test:watch": "jest --watch", 12 | "storybook": "storybook dev -p 6006", 13 | "build-storybook": "storybook build" 14 | }, 15 | "dependencies": { 16 | "autoprefixer": "10.4.14", 17 | "next": "13.3.0", 18 | "postcss": "8.4.21", 19 | "react": "18.2.0", 20 | "react-dom": "18.2.0", 21 | "tailwindcss": "3.3.1" 22 | }, 23 | "devDependencies": { 24 | "@storybook/addon-essentials": "7.0.5", 25 | "@storybook/addon-interactions": "7.0.5", 26 | "@storybook/addon-links": "7.0.5", 27 | "@storybook/blocks": "7.0.5", 28 | "@storybook/manager-api": "7.0.5", 29 | "@storybook/nextjs": "7.0.5", 30 | "@storybook/react": "7.0.5", 31 | "@storybook/testing-library": "0.0.14-next.2", 32 | "@storybook/theming": "7.0.5", 33 | "@testing-library/jest-dom": "5.16.5", 34 | "@testing-library/react": "14.0.0", 35 | "eslint": "8.38.0", 36 | "eslint-config-next": "13.3.0", 37 | "eslint-config-prettier": "8.8.0", 38 | "eslint-plugin-storybook": "0.6.11", 39 | "jest": "29.5.0", 40 | "jest-environment-jsdom": "29.5.0", 41 | "prettier": "2.8.7", 42 | "prettier-plugin-tailwindcss": "0.2.7", 43 | "storybook": "7.0.5" 44 | }, 45 | "engines": { 46 | "node": "^16 || ^17 || ^18" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /components/NavCard/NavCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Inter } from 'next/font/google'; 4 | 5 | const inter = Inter({ subsets: ['latin'] }); 6 | 7 | /** 8 | * > It will show a card with a URL. Clicking on the card will navigate the user to that URL. 9 | */ 10 | 11 | const NavCard = ({ title, description, href }) => { 12 | return ( 13 | 19 |

20 | {title}{' '} 21 | 22 | -> 23 | 24 |

25 |

{description}

26 |
27 | ); 28 | }; 29 | 30 | NavCard.defaultProps = { 31 | title: '', 32 | description: '', 33 | href: '', 34 | }; 35 | 36 | NavCard.propTypes = { 37 | /** 38 | * Title to be shown in the card. 39 | */ 40 | title: PropTypes.string, 41 | /** 42 | * Description to be shown in the card. 43 | */ 44 | description: PropTypes.string, 45 | /** 46 | * URL of the page which users will be redirected to after clicking the card. 47 | */ 48 | href: PropTypes.string, 49 | }; 50 | 51 | export default NavCard; 52 | -------------------------------------------------------------------------------- /public/stories/assets/repo.svg: -------------------------------------------------------------------------------- 1 | illustration/repo -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import NavCard from '@/components/NavCard'; 2 | import CodeBlock from '@/components/CodeBlock'; 3 | import BrandImage from '@/components/BrandImage'; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | 9 | 10 |
11 | 12 | Get started by editing  13 | pages/index.js 14 | 15 |
16 | 17 |
18 | 23 | 28 | 33 | 38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /public/stories/assets/plugin.svg: -------------------------------------------------------------------------------- 1 | illustration/plugin -------------------------------------------------------------------------------- /public/stories/assets/stackalt.svg: -------------------------------------------------------------------------------- 1 | illustration/stackalt -------------------------------------------------------------------------------- /components/Introduction.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/blocks'; 2 | import Image from 'next/image'; 3 | 4 | import Code from '/stories/assets/code-brackets.svg'; 5 | import Colors from '/stories/assets/colors.svg'; 6 | import Comments from '/stories/assets/comments.svg'; 7 | import Direction from '/stories/assets/direction.svg'; 8 | import Flow from '/stories/assets/flow.svg'; 9 | import Plugin from '/stories/assets/plugin.svg'; 10 | import Repo from '/stories/assets/repo.svg'; 11 | import StackAlt from '/stories/assets/stackalt.svg'; 12 | 13 | 14 | 15 | 120 | 121 | # Welcome to Storybook 122 | 123 | Storybook helps you build UI components in isolation from your app's business logic, data, and context. 124 | That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA. 125 | 126 | Browse example stories now by navigating to them in the sidebar. 127 | View their code in the `stories` directory to learn how they work. 128 | We recommend building UIs with a [**component-driven**](https://componentdriven.org) process starting with atomic components and ending with pages. 129 | 130 |
Configure
131 | 132 | 186 | 187 |
Learn
188 | 189 | 227 | 228 |
229 | TipEdit the Markdown in stories/Introduction.mdx 230 |
231 | -------------------------------------------------------------------------------- /public/stories/assets/colors.svg: -------------------------------------------------------------------------------- 1 | illustration/colors -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nextjs-tailwind-storybook-with-ci-cd 2 | 3 | This repository is built to provide a ready-to-go template for the projects that are using the following tech stacks: 4 | 5 | 1. NextJS 6 | 2. Tailwind 7 | 3. Storybook 8 | 4. Jest 9 | 5. React Testing Library 10 | 6. Eslint 11 | 7. Prettier 12 | 13 | This template will provide the full setup under the hood and will save a big portion of your time. You have to just create your new repo using this template. 14 | 15 | ## Table of Contents 16 | 17 | - [nextjs-tailwind-storybook-with-ci-cd](#nextjs-tailwind-storybook-with-ci-cd) 18 | - [Table of Contents](#table-of-contents) 19 | - [How to Create Repository Using This Template?](#how-to-create-repository-using-this-template) 20 | - [Package Versions](#package-versions) 21 | - [Available Commands](#available-commands) 22 | - [Folder Structure](#folder-structure) 23 | - [Change NextJS Config](#change-nextjs-config) 24 | - [Change Tailwind Config](#change-tailwind-config) 25 | - [Change Unit Test Config](#change-unit-test-config) 26 | - [Change Eslint Config](#change-eslint-config) 27 | - [Change Eslint Config](#change-eslint-config-1) 28 | - [Change Storybook Config](#change-storybook-config) 29 | - [CI/CD in This App](#cicd-in-this-app) 30 | - [Storybook doesn't Support Some NextJS Features](#storybook-doesnt-support-some-nextjs-features) 31 | - [License](#license) 32 | - [Deployment Guideline](#deployment-guideline) 33 | - [How to Deploy NextJS App on Vercel](#how-to-deploy-nextjs-app-on-vercel) 34 | 35 | ## How to Create Repository Using This Template? 36 | 37 | 1. Click on [**this link to generate a new repo using this template**](https://github.com/Showrin/nextjs-tailwind-storybook-with-ci-cd/generate). 38 | 2. Give a name to your repo. 39 | 3. Provide a meaningfull description. 40 | 4. You can keep the **Include all branches** options unchecked. 41 | 5. Click on **Create repository from template** button and boom :boom:. Your repository is ready. 42 | 6. Now if you clone the repo in your PC and run `yarn`, all the dependency will be installed. 43 | 7. Then running `yarn dev` will start the app at **http://localhost:3000**. 44 | 8. running `yarn storybook` will start the storybook of your app at **http://localhost:6006**. 45 | 46 | ## Package Versions 47 | 48 | Following versions were installed when this template was built. 49 | 50 | | Package Name | Version | 51 | | :------------------------ | ------------: | 52 | | NodeJS | **`16.10.0`** | 53 | | NextJS | **`13.3.0`** | 54 | | react | **`18.2.0`** | 55 | | react-dom | **`18.2.0`** | 56 | | tailwindcss | **`3.3.1`** | 57 | | postcss | **`8.4.21`** | 58 | | jest | **`29.5.0`** | 59 | | jest-environment-jsdom | **`29.5.0`** | 60 | | @testing-library/jest-dom | **`5.16.5`** | 61 | | @testing-library/react | **`14.0.0`** | 62 | | eslint | **`8.38.0`** | 63 | | eslint-config-next | **`13.3.0`** | 64 | | eslint-config-prettier | **`8.8.0`** | 65 | | prettier | **`2.8.7`** | 66 | | storybook | **`7.0.5`** | 67 | 68 | ## Available Commands 69 | 70 | There are some commands preadded to this template. These commands will help to execute basic tasks. 71 | 72 | 1. Following command will start the dev server at port `3000` and we can visit the site at **http://localhost:3000**. 73 | 74 | ``` 75 | yarn dev 76 | ``` 77 | 78 | 2. Following command will create a production build of the site. 79 | 80 | ``` 81 | yarn build 82 | ``` 83 | 84 | 3. Following command will start a server at port `3000` with the production build (created using `yarn build`) and we can visit the site at **http://localhost:3000**. 85 | 86 | ``` 87 | yarn start 88 | ``` 89 | 90 | 4. Following command will check any linting issue according to the eslint rules set in the `.eslintrc.json` file. 91 | 92 | ``` 93 | yarn lint 94 | ``` 95 | 96 | 5. Following command will execute all the unit tests (file with `*.test.jsx`, `*.test.tsx`, `*.test.js` or `*.test.ts` extention) and check whether all the tests pass or not. 97 | 98 | ``` 99 | yarn test 100 | ``` 101 | 102 | 6. Following command will execute unit tests (file with `*.test.jsx`, `*.test.tsx`, `*.test.js` or `*.test.ts` extention) and check whether all the tests pass or not in the real-time. 103 | 104 | ``` 105 | yarn test:watch 106 | ``` 107 | 108 | 7. Following command will start the storybook server at port 6006 and we can visit the story at **http://localhost:6006**. 109 | 110 | ``` 111 | yarn storybook 112 | ``` 113 | 114 | 8. Following command will create a static web application capable of being served by any web server. For more info please visit [**Publish Storybook**](https://storybook.js.org/docs/react/sharing/publish-storybook). 115 | 116 | ``` 117 | yarn build-storybook 118 | ``` 119 | 120 | ## Folder Structure 121 | 122 | Before working on a project, we should know about its folder structure. This helps a lot to understand where to find which components or portion of codes. In this template, the folder structure was tried to be kept as simple as possible. 123 | 124 | 1. **.github/workflows**: This folder is used to **keep all the files related to github actions**. We've already setup a basic CI/CD in the template and named the file `tests.yml`. We can create more yml files for different actions. 125 | 2. **.storybook**: All the storybook related config files like theme, seo, preview, manager etc live inside this folder. 126 | 3. **components**: In our web app, we'll have to build many reusable components like button, input, modal etc. This is the place where we can keep all our reusable components. While creating a component we have tto ensure the following rules have been followed in order to maintain the consistency. 127 | 128 | 1. Each **component must have a folder and index.js** file inside the folder. For example: if we are creating a button component, the structure should be: 129 | 130 | ``` 131 | components 132 | | - Buttton 133 | | - Button.jsx 134 | | - index.js 135 | ``` 136 | 137 | Then we can access the component from outside using `./componets/Button`. 138 | 139 | 2. If a component has some sub-components, then we can keep them in a folder named `subComponents` inside that component. 140 | 141 | ``` 142 | components 143 | | - Buttton 144 | | - subComponents 145 | | - ButtonOverlay 146 | | - ButtonOverlay.jsx 147 | | - index.js 148 | | - Button.jsx 149 | | - index.js 150 | ``` 151 | 152 | 3. Each component must have a story file (for storybook). Story file names should be in `.stories.jsx` this format. 153 | 154 | ``` 155 | components 156 | | - Buttton 157 | | - subComponents 158 | | - ButtonOverlay 159 | | - ButtonOverlay.jsx 160 | | - index.js 161 | | - Button.jsx 162 | | - Button.stories.jsx 163 | | - index.js 164 | ``` 165 | 166 | 4. Each component and sub-components must have a test file (for unit testing). Test file names should be in `.test.jsx` this format. 167 | 168 | ``` 169 | components 170 | | - Buttton 171 | | - subComponents 172 | | - ButtonOverlay 173 | | - ButtonOverlay.jsx 174 | | - ButtonOverlay.test.jsx 175 | | - index.js 176 | | - Button.jsx 177 | | - Button.stories.jsx 178 | | - Button.test.jsx 179 | | - index.js 180 | ``` 181 | 182 | That's a general set of rules for the `components` folder. But of course, you can set your own rules and structure. In that case, you might have to change storybook, jest, and other configs. 183 | 184 | 4. **pages**: This folder keeps all the page and api related files of our app. It's a basic feature provided by **NextJS**. To learn more about them, you can read the following docs from NextJS. 185 | 186 | 1. [NextJS Pages](https://nextjs.org/docs/basic-features/pages) 187 | 2. [NextJS API Routes](https://nextjs.org/docs/api-routes/introduction) 188 | 189 | 5. **public**: This folder is also provided by **NextJS**. It keeps all the static assets like images, icons, audios, pdfs etc. To learn more about this folder, visit [**Static File Serving in NextJs**](https://nextjs.org/docs/basic-features/static-file-serving). 190 | 6. **styles**: All the global css or scss files will be kept in this folder. You can keep the local files too with an appropiate folder structure in it. 191 | 192 | ## Change NextJS Config 193 | 194 | If you need to change settings of nextjs, then `next.config.js` and `jsconfig.json` is the place where you'll have to work. Currently, we've done a bare minimum setup in both files. 195 | 196 | But in the jsconfig.json file, we've setup a path alias for the root directory. **You can access the root directory using `@/` this alias**. To learn more about it, read [**Absolute Imports and Module path aliases in NextJS**](https://nextjs.org/docs/advanced-features/module-path-aliases). Also, you can learn more about `next.config.js` file from [**next.config.js Doc**](https://nextjs.org/docs/api-reference/next.config.js/introduction). 197 | 198 | ## Change Tailwind Config 199 | 200 | If you need to change settings of tailwind, then `postcss.config.js` and `tailwind.config.js` is the place where you'll have to work. You can modify or extend the tailwind theme in `tailwind.config.js` file. For more info, you can read [**Tailwind Configuration**](https://tailwindcss.com/docs/configuration) doc. 201 | 202 | ## Change Unit Test Config 203 | 204 | Basically, we can modify jest unit testing setup using `jest.config.js` and `jest.setup.js` file. To learn more about it, you can go through [**Jest and React Testing Library in NextJS**](https://nextjs.org/docs/testing#jest-and-react-testing-library). 205 | 206 | ## Change Eslint Config 207 | 208 | We can use `.eslintrc.json` file in order to add or remove any eslint rule. For more details, you can read [**ESLint Configuration Files Doc**](https://eslint.org/docs/latest/use/configure/configuration-files). 209 | 210 | ## Change Eslint Config 211 | 212 | `.prettierrc.json` file is there to change prettier config of the app. We've added some configs in this file. To remove or add any config, you can go through [**Prettier Configuration File Doc**](https://prettier.io/docs/en/configuration.html). 213 | 214 | ## Change Storybook Config 215 | 216 | All the config files of storybook are kept in the `.storybook` folder. 217 | 218 | All the project related configs for storybook are in `main.js` file. If you need to change anything in the storybook preview section, then `preview.js` file is the appropiate one. Learn more from [**Configure Storybook Doc**](https://storybook.js.org/docs/react/configure/overview). 219 | 220 | To modify the theme, we may have to work in `appTheme.js` and `manager.js` files. To learn more about theming, visit [**Storybook Theming Doc**](https://storybook.js.org/docs/react/configure/theming). 221 | 222 | If we want to do any SEO related changes, then we may have to modify `manager-head.html` or `main.js` files. To read more about it, visit [**Search engine optimization (SEO) Doc**](https://storybook.js.org/docs/react/sharing/publish-storybook#search-engine-optimization-seo). 223 | 224 | If we want to access any assets in the `public` folder, then we don't have to mention `public` in the src url. You can emit that. For example, we have an image `xyz.png` in public folder. Then we can access this image from any story using `./xyz.png`. Learn more about it from [**Serving static files via Storybook Configuration Doc**](https://storybook.js.org/docs/ember/configure/images-and-assets#serving-static-files-via-storybook-configuration). 225 | 226 | If we want to write detailed descriptions about our components, props and stories we can use JSDoc comments. To learn more please, visit the following docs from Storybook. 227 | 228 | 1. [**Writing descriptions in Storybook**](https://storybook.js.org/docs/react/api/doc-block-description) 229 | 2. [**Writing descriptions of Props in Storybook**](https://storybook.js.org/docs/react/api/argtypes#grouping) 230 | 231 | ## CI/CD in This App 232 | 233 | A github action has been setup in `tests.yml` file inside `.github` folder. This action runs js linting and unit test every time a new commit has been pushed to origin. You can write more github actions here as per your need. 234 | 235 | ## Storybook doesn't Support Some NextJS Features 236 | 237 | Currently, storybook doesn't support some features (TurboPack, Server Component, SWC etc) of `NextJS v13`. But if you are not using these features, then there shouldn't be any problem with storybook. [**[Source]**](https://storybook.js.org/docs/ember/configure/frameworks#nextjs-13-doesnt-work-with-storybook) 238 | 239 | ## License 240 | 241 | Being an open source repository, this repo has a MIT License in it. But when you are using this template, you should have to use your own license here :rotating_light::rotating_light::rotating_light:. You can easily generate this. You can read [**Licensing a repository Doc**](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository). 242 | 243 | ## Deployment Guideline 244 | 245 | ### How to Deploy NextJS App on Vercel 246 | 247 | 1. Visit [**Vercel's Dashboard**](https://vercel.com/new). 248 | 2. Under **`Import Git Repository`** section, click **`Continue with Github`** button. 249 | 3. Install **`Vercel`** in your GitHub account when prompted. 250 | 4. Select your **`NextJs app`** repository and click **`Import`**. 251 | 5. Add environment variables (if present) from your **`NextJs app`**'s **`.env`** file under **`Environment Variables`** tab in **`Configure Project`** section. 252 | 6. Click **`Deploy`**. 253 | 7. On successful deployment, you will be directed to a new url with a preview image of your deployed **`NextJs app`**. 254 | 8. Click on the preview image. 255 | 9. You will be directed to a url that looks like **`[DOMAIN-NAME].vercel.app`**. 256 | --------------------------------------------------------------------------------