├── .commitlintrc.ts
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── config.yml
├── pull_request_template.md
└── workflows
│ └── main.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .lintstagedrc
├── .prettierignore
├── .prettierrc
├── .storybook
├── main.ts
├── manager.ts
├── preview.tsx
└── theme.ts
├── LICENSE
├── README.md
├── eslint.config.mjs
├── misc
└── demo-tab.gif
├── package-lock.json
├── package.json
├── scripts
├── rollup.config.ts
├── tsconfig.base.json
├── tsconfig.build.json
└── tsconfig.dev.json
├── src
├── index.ts
├── react-demo-tab
│ ├── DemoTab.tsx
│ ├── img
│ │ ├── css.png
│ │ ├── jsx.png
│ │ └── sass.png
│ ├── types.public.ts
│ └── types.ts
└── types
│ └── images.d.ts
├── stories
├── ButtonGreen
│ ├── ButtonGreen.css
│ ├── ButtonGreen.tsx
│ └── _ButtonGreen.tsx
├── ButtonRed
│ ├── ButtonRed.scss
│ ├── ButtonRed.tsx
│ └── _ButtonRed.tsx
├── Counter
│ ├── Counter.tsx
│ └── _Counter.tsx
├── Default
│ ├── Default.jsx
│ └── _Default.tsx
├── DemoTab.scss
└── DemoTab.stories.tsx
└── tsconfig.json
/.commitlintrc.ts:
--------------------------------------------------------------------------------
1 | import type { UserConfig } from '@commitlint/types';
2 |
3 | const commitlintConfig: UserConfig = {
4 | extends: ['@commitlint/config-conventional'],
5 | };
6 |
7 | module.exports = commitlintConfig;
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 'Bug Report'
2 | description: 'File a bug report'
3 | body:
4 | - type: 'textarea'
5 | id: 'description'
6 | attributes:
7 | label: 'Description'
8 | description: 'A clear and concise description of what the bug is.'
9 | placeholder: |
10 | Bug description
11 | validations:
12 | required: true
13 | - type: 'input'
14 | id: 'reproduction_link'
15 | attributes:
16 | label: 'Link to Reproduction'
17 | description: |
18 | Please provide demo in online code editor [CodeSandbox](https://codesandbox.io/) or similar.
19 | Issues without a reproduction link are likely to stall.
20 | placeholder: 'https://codesandbox.io/'
21 | validations:
22 | required: true
23 | - type: 'textarea'
24 | id: 'reproduction_steps'
25 | attributes:
26 | label: 'Steps to reproduce'
27 | description: |
28 | Steps to reproduce the behavior:
29 | value: |
30 | 1. Go to '...'
31 | 2. Click on '...'
32 | 3. Scroll down to '...'
33 | 4. See error
34 | - type: 'textarea'
35 | id: 'expected_behavior'
36 | attributes:
37 | label: 'Expected behavior'
38 | description: 'A clear and concise description of what you expected to happen.'
39 | validations:
40 | required: true
41 | - type: 'textarea'
42 | id: 'code_snippets'
43 | attributes:
44 | label: 'Code snippets'
45 | description: |
46 | If applicable, add code samples to help explain your problem.
47 | value: |
48 | ```jsx
49 |
50 |
51 |
52 | ```
53 | - type: 'input'
54 | id: 'lib-version'
55 | attributes:
56 | label: 'React DemoTab Version'
57 | description: 'The version of library you use.'
58 | placeholder: '1.5.36'
59 | validations:
60 | required: true
61 | - type: 'input'
62 | id: 'browser'
63 | attributes:
64 | label: 'Browser'
65 | description: 'The browser this issue occurred with.'
66 | placeholder: 'Google Chrome 93'
67 | - type: 'checkboxes'
68 | id: 'operating-system'
69 | attributes:
70 | label: 'Operating System'
71 | description: 'The operating system(s) this issue occurred with.'
72 | options:
73 | - label: 'macOS'
74 | - label: 'Windows'
75 | - label: 'Linux'
76 | - type: 'textarea'
77 | id: 'additional-information'
78 | attributes:
79 | label: 'Additional Information'
80 | description: |
81 | Add any other context about the problem here.
82 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Ask a question
4 | url: https://github.com/demozap/react-demo-tab/discussions
5 | about: Ask questions and discuss topics with other community members
6 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### 🚀 Pull Request Template
2 |
3 | **Description**
4 |
5 | Feature Proposal ...
6 |
7 | OR
8 |
9 | Fixes a bug where '...' happened when '...'
10 |
11 | **Checklist**
12 |
13 | - [ ] Commit messages should follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) as much as possible. After staging your changes please run `npm run commit`
14 | - [ ] Lint passing - `npm run lint`
15 | - [ ] Extended the Storybook demo page / README / documentation, if necessary
16 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | env:
9 | NODE_VERSION: '24.x'
10 | FOLDER_PATH_STORYBOOK_BUILD: ./build-storybook-static
11 |
12 | jobs:
13 | build:
14 | name: Build 🏗️
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - name: Checkout 🛎️
19 | uses: actions/checkout@v4
20 |
21 | - uses: actions/setup-node@v4
22 | with:
23 | node-version: ${{ env.NODE_VERSION }}
24 |
25 | - name: Cache dependencies ⚡
26 | id: cache_dependencies
27 | uses: actions/cache@v4
28 | with:
29 | path: node_modules
30 | key: node-modules-${{ hashFiles('package-lock.json') }}
31 |
32 | - name: Install dependencies 🔧
33 | if: steps.cache_dependencies.outputs.cache-hit != 'true'
34 | run: npm ci
35 |
36 | - name: Build 🏗️
37 | run: npm run build
38 |
39 | lint:
40 | name: Lint ✅
41 | runs-on: ubuntu-latest
42 |
43 | steps:
44 | - name: Checkout 🛎️
45 | uses: actions/checkout@v4
46 |
47 | - uses: actions/setup-node@v4
48 | with:
49 | node-version: ${{ env.NODE_VERSION }}
50 |
51 | - name: Cache dependencies ⚡
52 | id: cache_dependencies
53 | uses: actions/cache@v4
54 | with:
55 | path: node_modules
56 | key: node-modules-${{ hashFiles('package-lock.json') }}
57 |
58 | - name: Install dependencies 🔧
59 | if: steps.cache_dependencies.outputs.cache-hit != 'true'
60 | run: npm ci
61 |
62 | - name: Lint ✅
63 | run: npm run lint
64 |
65 | tsc:
66 | name: TypeScript Compiler 🔎
67 | runs-on: ubuntu-latest
68 |
69 | steps:
70 | - name: Checkout 🛎️
71 | uses: actions/checkout@v4
72 |
73 | - uses: actions/setup-node@v4
74 | with:
75 | node-version: ${{ env.NODE_VERSION }}
76 |
77 | - name: Cache dependencies ⚡
78 | id: cache_dependencies
79 | uses: actions/cache@v4
80 | with:
81 | path: node_modules
82 | key: node-modules-${{ hashFiles('package-lock.json') }}
83 |
84 | - name: Install dependencies 🔧
85 | if: steps.cache_dependencies.outputs.cache-hit != 'true'
86 | run: npm ci
87 |
88 | - name: TypeScript Compiler 🔎
89 | run: npm run tsc
90 |
91 | storybook:
92 | name: Deploy Storybook 🚀
93 | runs-on: ubuntu-latest
94 | needs: [build, lint, tsc]
95 | if: github.ref == 'refs/heads/main'
96 |
97 | steps:
98 | - name: Checkout 🛎️
99 | uses: actions/checkout@v4
100 |
101 | - uses: actions/setup-node@v4
102 | with:
103 | node-version: ${{ env.NODE_VERSION }}
104 |
105 | - name: Cache dependencies ⚡
106 | id: cache_dependencies
107 | uses: actions/cache@v4
108 | with:
109 | path: node_modules
110 | key: node-modules-${{ hashFiles('package-lock.json') }}
111 |
112 | - name: Install dependencies 🔧
113 | if: steps.cache_dependencies.outputs.cache-hit != 'true'
114 | run: npm ci
115 |
116 | - name: Build 🏗️
117 | run: npm run storybook-build
118 |
119 | - name: Deploy 🚀
120 | uses: JamesIves/github-pages-deploy-action@v4
121 | with:
122 | branch: gh-pages
123 | folder: ${{ env.FOLDER_PATH_STORYBOOK_BUILD }}
124 | clean: true
125 |
126 | release-npm:
127 | name: Release npm 🚀
128 | runs-on: ubuntu-latest
129 | needs: [build, lint, tsc]
130 | if: github.ref == 'refs/heads/main'
131 | env:
132 | NODE_ENV: 'production'
133 |
134 | steps:
135 | - name: Checkout 🛎️
136 | uses: actions/checkout@v4
137 |
138 | - uses: actions/setup-node@v4
139 | with:
140 | node-version: ${{ env.NODE_VERSION }}
141 |
142 | - name: Cache dependencies ⚡
143 | id: cache_dependencies
144 | uses: actions/cache@v4
145 | with:
146 | path: node_modules
147 | key: node-modules-${{ hashFiles('package-lock.json') }}
148 |
149 | - name: Install dependencies 🔧
150 | if: steps.cache_dependencies.outputs.cache-hit != 'true'
151 | run: npm ci
152 |
153 | - name: Build 🏗️
154 | run: npm run build
155 |
156 | - name: Release 🚀
157 | env:
158 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
159 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
160 | run: npm run release
161 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | dist
4 | build-storybook-static
5 | compiled
6 |
7 | bundle-analysis.html
8 |
9 | .rpt2_cache
10 |
11 | npm-debug.log*
12 | .DS_Store
13 | .env
14 | *.log
15 | .vscode
16 | .idea
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | npx --no-install commitlint --edit "$1"
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npm run lint-staged-husky
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*": ["prettier --write --ignore-unknown"],
3 | "*.{js,jsx,ts,tsx}": ["eslint --max-warnings 0"]
4 | }
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "semi": true,
4 | "singleQuote": true,
5 | "trailingComma": "all"
6 | }
7 |
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import path, { dirname } from 'path';
2 | import { fileURLToPath } from 'url';
3 |
4 | import type { StorybookConfig } from '@storybook/react-webpack5';
5 |
6 | const __filename = fileURLToPath(import.meta.url);
7 | const __dirname = dirname(__filename);
8 |
9 | const storybookConfig: StorybookConfig = {
10 | stories: ['../stories/**/*.stories.tsx'],
11 | framework: {
12 | name: '@storybook/react-webpack5',
13 | options: {},
14 | },
15 |
16 | // JSX transform no longer requires importing React explicitly in every file.
17 | swc: () => ({
18 | jsc: {
19 | transform: {
20 | react: {
21 | runtime: 'automatic',
22 | },
23 | },
24 | },
25 | }),
26 |
27 | webpackFinal: (config) => {
28 | config.module?.rules?.push({
29 | test: /\.scss$/,
30 | use: ['style-loader', 'css-loader', 'sass-loader'],
31 | include: path.resolve(__dirname, '../'),
32 | });
33 |
34 | // Resolve absolute imports
35 | config.resolve?.modules?.push(path.resolve(process.cwd(), 'src'));
36 |
37 | return config;
38 | },
39 |
40 | addons: ['@storybook/addon-webpack5-compiler-swc', '@storybook/addon-styling'],
41 | };
42 |
43 | // eslint-disable-next-line import-x/no-default-export
44 | export default storybookConfig;
45 |
--------------------------------------------------------------------------------
/.storybook/manager.ts:
--------------------------------------------------------------------------------
1 | import { addons } from '@storybook/manager-api';
2 |
3 | import { theme } from './theme';
4 |
5 | addons.setConfig({
6 | theme,
7 | toolbar: {
8 | title: { hidden: false },
9 | zoom: { hidden: false },
10 | eject: { hidden: false },
11 | copy: { hidden: false },
12 | fullscreen: { hidden: false },
13 | },
14 | bottomPanelHeight: 0,
15 | panelPosition: false,
16 | });
17 |
--------------------------------------------------------------------------------
/.storybook/preview.tsx:
--------------------------------------------------------------------------------
1 | import type { Preview } from '@storybook/react';
2 |
3 | const preview: Preview = {
4 | parameters: {
5 | actions: { argTypesRegex: '^on[A-Z].*' },
6 | controls: {
7 | // Description toggle
8 | // expanded: true,
9 | matchers: {
10 | color: /(background|color)$/i,
11 | date: /Date$/,
12 | },
13 | },
14 | },
15 | };
16 |
17 | // eslint-disable-next-line import-x/no-default-export
18 | export default preview;
19 |
--------------------------------------------------------------------------------
/.storybook/theme.ts:
--------------------------------------------------------------------------------
1 | import { create } from '@storybook/theming';
2 |
3 | export const theme = create({
4 | base: 'light',
5 | brandTitle: 'React DemoTab 📑',
6 | brandUrl: 'https://github.com/demozap/react-demo-tab',
7 | });
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Marko Kosir
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React DemoTab 📑
2 |
3 | [![npm version][npm-badge]][npm-url]
4 | [![CI][build-badge]][build-url]
5 | [![semantic-release][semantic-badge]][semantic-url]
6 | [![TypeScript][typescript-badge]][typescript-url]
7 |
8 | _Easily create React demo components_
9 |
10 | ## [Demo](https://demozap.github.io/react-demo-tab)
11 |
12 | ## Install
13 |
14 | ```bash
15 | npm install react-demo-tab
16 | ```
17 |
18 | ## Example
19 |
20 | [](https://demozap.github.io/react-demo-tab/?path=/story/examples--button-green)
21 |
22 | ```jsx
23 | import ReactDOM from 'react-dom';
24 | import DemoTab from 'react-demo-tab';
25 | import DemoComponent from './ButtonGreen';
26 |
27 | const demoCode = `
28 | import './ButtonGreen.css';
29 |
30 | const ButtonGreen = () => ;
31 | export default ButtonGreen;`;
32 |
33 | const demoStyle = `
34 | .btn-green {
35 | background-color: green;
36 | font-size: 14px;
37 | padding: 12px 26px;
38 | border-radius: 6px;
39 | }`;
40 |
41 | const App = () => {
42 | return (
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | ReactDOM.render(, document.getElementById('root'));
50 | ```
51 |
52 | ## Props
53 |
54 | Create demo of component that is passed as a child.
55 |
56 | Below is the complete list of possible props and their options:
57 |
58 | > ▶︎ indicates optional prop with default value
59 |
60 | **code**: string
61 | Demo code. Required.
62 |
63 | **style**: string ▶︎ `undefined`
64 | Demo style.
65 |
66 | **codeExt**: 'jsx' | 'tsx' ▶︎ `jsx`
67 | Code file extension for image to be displayed.
68 |
69 | **styleExt**: 'css' | 'scss' ▶︎ `css`
70 | Style file extension for image to be displayed.
71 |
72 | ## Create demos with CLI
73 |
74 | Instead of manually creating demos, automate the process with [DemoZap CLI](https://github.com/demozap/demozap).
75 |
76 | ## Development
77 |
78 | _Easily set up a local development environment!_
79 |
80 | Build project and start storybook on [localhost](http://localhost:9009):
81 |
82 | - clone
83 | - `npm install`
84 | - `npm start`
85 |
86 | **Start coding!** 🎉
87 |
88 | ## Built with DemoTab
89 |
90 | - [React Tilt](https://github.com/mkosir/react-parallax-tilt) - [DemoTab](https://mkosir.github.io/react-parallax-tilt)
91 | - [Mighty Mouse](https://github.com/mkosir/react-hook-mighty-mouse) - [DemoTab](https://mkosir.github.io/react-hook-mighty-mouse)
92 | - [Magnetic Board](https://github.com/mkosir/react-magnetic-board) - [DemoTab](https://mkosir.github.io/react-magnetic-board)
93 |
94 | ## Contributing
95 |
96 | All contributions are welcome!
97 |
98 | [npm-url]: https://www.npmjs.com/package/react-demo-tab
99 | [npm-badge]: https://img.shields.io/npm/v/react-demo-tab.svg
100 | [build-badge]: https://github.com/demozap/react-demo-tab/actions/workflows/main.yml/badge.svg
101 | [build-url]: https://github.com/demozap/react-demo-tab/actions/workflows/main.yml
102 | [semantic-badge]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
103 | [semantic-url]: https://github.com/semantic-release/semantic-release
104 | [typescript-badge]: https://badges.frapsoft.com/typescript/code/typescript.svg?v=101
105 | [typescript-url]: https://github.com/microsoft/TypeScript
106 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import eslint from '@eslint/js';
2 | import eslintConfigPrettier from 'eslint-config-prettier';
3 | import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript';
4 | import * as eslintPluginImportX from 'eslint-plugin-import-x';
5 | import eslintPluginJsxA11y from 'eslint-plugin-jsx-a11y';
6 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
7 | import eslintPluginReact from 'eslint-plugin-react';
8 | import * as eslintPluginReactHooks from 'eslint-plugin-react-hooks';
9 | import * as tseslint from 'typescript-eslint';
10 |
11 | export default tseslint.config(
12 | eslint.configs.recommended,
13 |
14 | eslintPluginImportX.flatConfigs.recommended,
15 | eslintPluginImportX.flatConfigs.typescript,
16 | ...tseslint.configs.strictTypeChecked,
17 | ...tseslint.configs.stylisticTypeChecked,
18 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
19 | eslintPluginJsxA11y.flatConfigs.recommended,
20 | eslintPluginReact.configs.flat.recommended,
21 | eslintPluginPrettierRecommended,
22 | eslintConfigPrettier,
23 | // https://eslint.org/docs/latest/use/configure/configuration-files#excluding-files-with-ignores
24 | // When in their own config block without files, it tells ESLint to ignore those files.
25 | // When in a config block with files, it stops that specific config block from affecting those ignored files.
26 | {
27 | ignores: ['!.*', 'node_modules', 'dist', 'compiled', 'build'],
28 | },
29 | {
30 | languageOptions: {
31 | parserOptions: {
32 | projectService: true,
33 | tsconfigRootDir: import.meta.dirname,
34 | },
35 | },
36 | settings: {
37 | 'import-x/resolver-next': [createTypeScriptImportResolver()],
38 | react: { version: 'detect' },
39 | },
40 | },
41 | {
42 | files: ['**/*.{js,ts,jsx,tsx}'],
43 |
44 | plugins: {
45 | 'react-hooks': eslintPluginReactHooks,
46 | },
47 |
48 | rules: {
49 | ...eslintPluginReactHooks.configs.recommended.rules,
50 |
51 | 'react/react-in-jsx-scope': 'off',
52 |
53 | 'prefer-template': 'error',
54 | 'no-nested-ternary': 'error',
55 | 'no-unneeded-ternary': 'error',
56 | 'spaced-comment': 'error',
57 | 'no-console': 'error',
58 |
59 | '@typescript-eslint/ban-ts-comment': ['error', { 'ts-expect-error': 'allow-with-description' }],
60 | '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
61 | '@typescript-eslint/array-type': ['error', { default: 'generic' }],
62 | '@typescript-eslint/prefer-nullish-coalescing': 'error',
63 | '@typescript-eslint/no-unnecessary-condition': 'error',
64 | '@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
65 | '@typescript-eslint/restrict-plus-operands': 'off',
66 | '@typescript-eslint/restrict-template-expressions': 'off',
67 | '@typescript-eslint/consistent-type-imports': 'error',
68 | '@typescript-eslint/naming-convention': [
69 | 'error',
70 | {
71 | selector: 'typeAlias',
72 | format: ['PascalCase'],
73 | },
74 | {
75 | // Generic type parameter must start with letter T, followed by any uppercase letter.
76 | selector: 'typeParameter',
77 | format: ['PascalCase'],
78 | custom: { regex: '^T[A-Z]', match: true },
79 | },
80 | ],
81 |
82 | 'import-x/no-default-export': 'error',
83 | 'import-x/order': [
84 | 'error',
85 | {
86 | groups: ['builtin', 'external', 'internal', 'parent', 'sibling'],
87 | 'newlines-between': 'always',
88 | alphabetize: {
89 | order: 'asc',
90 | caseInsensitive: true,
91 | },
92 | },
93 | ],
94 | },
95 | },
96 | {
97 | files: ['stories/**/*'],
98 | rules: {
99 | 'import-x/no-default-export': 'off',
100 | },
101 | },
102 | );
103 |
--------------------------------------------------------------------------------
/misc/demo-tab.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demozap/react-demo-tab/c183c188aaf8896d6d39eb7c4737690625c9085f/misc/demo-tab.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-demo-tab",
3 | "version": "0.0.0",
4 | "description": "Easily create React demo components",
5 | "main": "./dist/index.umd.js",
6 | "module": "./dist/index.esm.js",
7 | "types": "./dist/index.d.ts",
8 | "files": [
9 | "dist",
10 | "README.md"
11 | ],
12 | "scripts": {
13 | "start": "npm run storybook",
14 | "prepare": "husky",
15 | "build": "rm -rf dist && rollup --config scripts/rollup.config.ts --configPlugin typescript --configImportAttributesKey with",
16 | "lint": "eslint --report-unused-disable-directives --max-warnings 0 .",
17 | "lint-fix": "eslint --fix .",
18 | "lint-staged-husky": "lint-staged",
19 | "tsc": "tsc -p scripts/tsconfig.dev.json",
20 | "format-lint": "prettier --config .prettierrc --check --ignore-unknown .",
21 | "format-fix": "prettier --config .prettierrc --write --ignore-unknown -l .",
22 | "commit": "git-cz",
23 | "storybook": "TS_NODE_PROJECT=scripts/tsconfig.dev.json storybook dev -p 9009 --quiet",
24 | "storybook-build": "storybook build -o build-storybook-static",
25 | "release": "semantic-release --branches main",
26 | "clean": "rm -rf node_modules coverage dist compiled build-storybook-static"
27 | },
28 | "devDependencies": {
29 | "@commitlint/cli": "19.8.1",
30 | "@commitlint/config-conventional": "19.8.1",
31 | "@eslint/js": "9.26.0",
32 | "@rollup/plugin-commonjs": "28.0.3",
33 | "@rollup/plugin-image": "3.0.3",
34 | "@rollup/plugin-terser": "0.4.4",
35 | "@rollup/plugin-typescript": "11.1.6",
36 | "@storybook/addon-styling": "2.0.0",
37 | "@storybook/addon-webpack5-compiler-swc": "1.0.5",
38 | "@storybook/manager-api": "8.4.6",
39 | "@storybook/react": "8.4.6",
40 | "@storybook/react-webpack5": "8.4.6",
41 | "@storybook/theming": "8.4.6",
42 | "@types/eslint": "9.6.1",
43 | "@types/react": "19.1.3",
44 | "@types/react-dom": "19.1.3",
45 | "@types/react-syntax-highlighter": "15.5.13",
46 | "commitizen": "4.3.1",
47 | "eslint": "9.26.0",
48 | "eslint-config-prettier": "10.1.5",
49 | "eslint-import-resolver-typescript": "4.3.4",
50 | "eslint-plugin-import-x": "4.11.1",
51 | "eslint-plugin-jsx-a11y": "6.10.2",
52 | "eslint-plugin-no-array-reduce": "1.0.62",
53 | "eslint-plugin-prettier": "5.4.0",
54 | "eslint-plugin-react": "7.37.5",
55 | "eslint-plugin-react-hooks": "5.2.0",
56 | "husky": "9.1.7",
57 | "lint-staged": "16.0.0",
58 | "prettier": "3.5.3",
59 | "react": "18.3.1",
60 | "react-dom": "18.3.1",
61 | "rollup": "4.40.2",
62 | "rollup-plugin-dts": "6.2.1",
63 | "sass": "1.88.0",
64 | "sass-loader": "16.0.5",
65 | "semantic-release": "24.2.3",
66 | "storybook": "8.4.6",
67 | "typescript": "5.8.3",
68 | "typescript-eslint": "8.32.0"
69 | },
70 | "peerDependencies": {
71 | "react": ">=18.0.0",
72 | "react-dom": ">=18.0.0"
73 | },
74 | "dependencies": {
75 | "@rehooks/local-storage": "2.4.5",
76 | "react-syntax-highlighter": "15.6.1",
77 | "react-tabs": "6.1.0"
78 | },
79 | "license": "MIT",
80 | "author": "Marko Kosir ",
81 | "homepage": "https://github.com/demozap/react-demo-tab",
82 | "repository": {
83 | "type": "git",
84 | "url": "git+https://github.com/demozap/react-demo-tab.git"
85 | },
86 | "bugs": {
87 | "url": "https://github.com/demozap/react-demo-tab/issues"
88 | },
89 | "keywords": [
90 | "react",
91 | "react-component",
92 | "demo-component",
93 | "demozap",
94 | "demo",
95 | "demo-tab",
96 | "demotab",
97 | "code",
98 | "tab"
99 | ]
100 | }
101 |
--------------------------------------------------------------------------------
/scripts/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import commonjs from '@rollup/plugin-commonjs';
2 | import image from '@rollup/plugin-image';
3 | import terser from '@rollup/plugin-terser';
4 | import typescript from '@rollup/plugin-typescript';
5 | import { defineConfig } from 'rollup';
6 | import { dts } from 'rollup-plugin-dts';
7 |
8 | import packageJson from '../package.json' with { type: 'json' };
9 |
10 | import tsConfig from './tsconfig.base.json' with { type: 'json' };
11 |
12 | const IS_PRODUCTION = process.env.NODE_ENV === 'production';
13 |
14 | const PATH_INPUT_FILE = 'src/index.ts';
15 | const PATH_TSCONFIG_BUILD = 'scripts/tsconfig.build.json';
16 |
17 | const rollupConfig = defineConfig([
18 | {
19 | input: PATH_INPUT_FILE,
20 | output: [
21 | {
22 | file: packageJson.main,
23 | name: packageJson.name,
24 | format: 'umd',
25 | sourcemap: !IS_PRODUCTION,
26 | globals: {
27 | react: 'React',
28 | 'react-dom': 'ReactDOM',
29 | 'react-tabs': 'react-tabs',
30 | 'react-syntax-highlighter': 'react-syntax-highlighter',
31 | '@rehooks/local-storage': '@rehooks/local-storage',
32 | 'react/jsx-runtime': 'jsxRuntime',
33 | 'react-syntax-highlighter/dist/esm/styles/prism': 'prism',
34 | },
35 | },
36 | {
37 | file: packageJson.module,
38 | format: 'esm',
39 | sourcemap: !IS_PRODUCTION,
40 | },
41 | ],
42 | plugins: [
43 | commonjs(),
44 | typescript({
45 | tsconfig: PATH_TSCONFIG_BUILD,
46 | sourceMap: !IS_PRODUCTION,
47 | }),
48 | terser({
49 | output: { comments: false },
50 | compress: {
51 | pure_getters: true,
52 | },
53 | toplevel: true,
54 | }),
55 | image(),
56 | ],
57 | external: [
58 | // Ensure dependencies are not bundled with the library
59 | ...Object.keys(packageJson.peerDependencies),
60 | ...Object.keys(packageJson.dependencies),
61 | 'react-tabs/style/react-tabs.css',
62 | 'react/jsx-runtime',
63 | 'react-syntax-highlighter/dist/esm/styles/prism',
64 | ],
65 | },
66 | {
67 | input: PATH_INPUT_FILE,
68 | output: { file: packageJson.types, format: 'esm' },
69 | plugins: [
70 | dts({
71 | compilerOptions: {
72 | baseUrl: './src',
73 | paths: tsConfig.compilerOptions.paths,
74 | },
75 | }),
76 | ],
77 | external: ['react-tabs/style/react-tabs.css'],
78 | },
79 | ]);
80 |
81 | // eslint-disable-next-line import-x/no-default-export
82 | export default rollupConfig;
83 |
--------------------------------------------------------------------------------
/scripts/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "outDir": "../compiled",
5 | "baseUrl": "../src",
6 | "paths": {
7 | ".": ["."]
8 | },
9 | "importHelpers": true,
10 | "jsx": "react-jsx",
11 | "lib": ["ESNext", "DOM"],
12 | "module": "ESNext",
13 | "moduleResolution": "Bundler",
14 | "target": "ES2020",
15 | "allowSyntheticDefaultImports": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "resolveJsonModule": true,
19 | "pretty": true,
20 | "sourceMap": false,
21 | "strict": true,
22 | "stripInternal": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "skipLibCheck": true,
25 | "esModuleInterop": true,
26 | "allowJs": true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/scripts/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "include": ["../src/**/*"],
4 | "exclude": ["../src/**/*.test.ts", "../src/**/*.test.tsx"]
5 | }
6 |
--------------------------------------------------------------------------------
/scripts/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | // Type check and lint all files and folders.
4 | // Explicitly include those starting with a dot (.) as they are ignored by default in glob patterns.
5 | "include": ["../.", "../.storybook/**/*", "../.commitlintrc.ts"],
6 | "exclude": ["../dist", "../node_modules"]
7 | }
8 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { DemoTab } from 'react-demo-tab/DemoTab';
2 |
3 | // Public exposed library types
4 | export * from 'react-demo-tab/types.public';
5 |
--------------------------------------------------------------------------------
/src/react-demo-tab/DemoTab.tsx:
--------------------------------------------------------------------------------
1 | import { useLocalStorage } from '@rehooks/local-storage';
2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
3 | import { prism } from 'react-syntax-highlighter/dist/esm/styles/prism';
4 | import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
5 | import 'react-tabs/style/react-tabs.css';
6 |
7 | import styleImgCSS from './img/css.png';
8 | import codeImg from './img/jsx.png';
9 | import styleImgSass from './img/sass.png';
10 | import type { LocalStorage } from './types';
11 | import type { DemoTabProps } from './types.public';
12 |
13 | const localStorageKey = 'react-demo-tab';
14 |
15 | export const DemoTab = ({ code, style, codeExt = 'jsx', styleExt = 'css', children }: DemoTabProps) => {
16 | const [tabIndex, setTabIndex] = useLocalStorage(localStorageKey, { mainTabIndex: 0, codeTabIndex: 0 });
17 |
18 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
19 | const styleImg = styleExt === 'css' ? styleImgCSS : styleImgSass;
20 |
21 | return (
22 | setTabIndex({ mainTabIndex: index, codeTabIndex: tabIndex.codeTabIndex })}
25 | >
26 |
27 | Demo
28 | Code
29 |
30 | {children}
31 |
32 | setTabIndex({ mainTabIndex: tabIndex.mainTabIndex, codeTabIndex: index })}
35 | >
36 |
37 |
38 | {/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */}
39 |
40 |
41 | {style && (
42 |
43 | {/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */}
44 |
45 |
46 | )}
47 |
48 |
49 |
50 | {code}
51 |
52 |
53 | {style && (
54 |
55 |
56 | {style}
57 |
58 |
59 | )}
60 |
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/src/react-demo-tab/img/css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demozap/react-demo-tab/c183c188aaf8896d6d39eb7c4737690625c9085f/src/react-demo-tab/img/css.png
--------------------------------------------------------------------------------
/src/react-demo-tab/img/jsx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demozap/react-demo-tab/c183c188aaf8896d6d39eb7c4737690625c9085f/src/react-demo-tab/img/jsx.png
--------------------------------------------------------------------------------
/src/react-demo-tab/img/sass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demozap/react-demo-tab/c183c188aaf8896d6d39eb7c4737690625c9085f/src/react-demo-tab/img/sass.png
--------------------------------------------------------------------------------
/src/react-demo-tab/types.public.ts:
--------------------------------------------------------------------------------
1 | export type DemoTabProps = {
2 | /**
3 | * Demo component.
4 | */
5 | children: React.ReactNode;
6 | /**
7 | * Demo code.
8 | */
9 | code: string;
10 | /**
11 | * Demo style.
12 | */
13 | style?: string;
14 | /**
15 | * Code file extension for image to be displayed.
16 | */
17 | codeExt?: 'jsx' | 'tsx';
18 | /**
19 | * Style file extension for image to be displayed.
20 | */
21 | styleExt?: 'css' | 'scss';
22 | };
23 |
--------------------------------------------------------------------------------
/src/react-demo-tab/types.ts:
--------------------------------------------------------------------------------
1 | export type LocalStorage = {
2 | mainTabIndex: number;
3 | codeTabIndex: number;
4 | };
5 |
--------------------------------------------------------------------------------
/src/types/images.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.png';
2 |
--------------------------------------------------------------------------------
/stories/ButtonGreen/ButtonGreen.css:
--------------------------------------------------------------------------------
1 | .btn-green {
2 | background-color: green;
3 | font-size: 14px;
4 | padding: 12px 26px;
5 | border-radius: 6px;
6 | cursor: pointer;
7 | -webkit-transition-duration: 0.2s;
8 | transition-duration: 0.2s;
9 | }
10 |
11 | .btn-green:hover {
12 | background-color: #4caf50;
13 | }
14 |
--------------------------------------------------------------------------------
/stories/ButtonGreen/ButtonGreen.tsx:
--------------------------------------------------------------------------------
1 | import './ButtonGreen.css';
2 |
3 | const ButtonGreen = () => ;
4 |
5 | export default ButtonGreen;
6 |
--------------------------------------------------------------------------------
/stories/ButtonGreen/_ButtonGreen.tsx:
--------------------------------------------------------------------------------
1 | import { DemoTab } from 'index';
2 |
3 | import Demo from './ButtonGreen';
4 |
5 | const code = `import './ButtonGreen.css';
6 |
7 | const ButtonGreen = () => ;
8 |
9 | export default ButtonGreen;`;
10 |
11 | const style = `.btn-green {
12 | background-color: green;
13 | font-size: 14px;
14 | padding: 12px 26px;
15 | border-radius: 6px;
16 | cursor: pointer;
17 | -webkit-transition-duration: 0.2s;
18 | transition-duration: 0.2s;
19 | }
20 |
21 | .btn-green:hover {
22 | background-color: #4caf50;
23 | }`;
24 |
25 | export const _ButtonGreen = () => (
26 |
27 |
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/stories/ButtonRed/ButtonRed.scss:
--------------------------------------------------------------------------------
1 | .btn-red {
2 | background-color: red;
3 | font-size: 14px;
4 | padding: 12px 26px;
5 | border-radius: 6px;
6 | cursor: pointer;
7 | -webkit-transition-duration: 0.2s;
8 | transition-duration: 0.2s;
9 |
10 | &:hover {
11 | background-color: #ca1f1f;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/stories/ButtonRed/ButtonRed.tsx:
--------------------------------------------------------------------------------
1 | import './ButtonRed.scss';
2 |
3 | const ButtonRed = () => ;
4 |
5 | export default ButtonRed;
6 |
--------------------------------------------------------------------------------
/stories/ButtonRed/_ButtonRed.tsx:
--------------------------------------------------------------------------------
1 | import { DemoTab } from 'index';
2 |
3 | import Demo from './ButtonRed';
4 |
5 | const code = `import './ButtonRed.scss';
6 |
7 | const ButtonRed = () => ;
8 |
9 | export default ButtonRed;`;
10 |
11 | const style = `.btn-red {
12 | background-color: red;
13 | font-size: 14px;
14 | padding: 12px 26px;
15 | border-radius: 6px;
16 | cursor: pointer;
17 | -webkit-transition-duration: 0.2s;
18 | transition-duration: 0.2s;
19 |
20 | &:hover {
21 | background-color: #ca1f1f;
22 | }
23 | }`;
24 |
25 | export const _ButtonRed = () => (
26 |
27 |
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/stories/Counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const Counter = () => {
4 | const [count, setCount] = useState(0);
5 |
6 | const onCountInc = () => setCount(count + 1);
7 |
8 | const msg = `Button clicked ${count.toString()} ${count > 1 ? 'times' : 'time'}`;
9 | const marginBottom = 10;
10 |
11 | return (
12 | <>
13 |
19 | {msg}
20 | >
21 | );
22 | };
23 |
24 | export default Counter;
25 |
--------------------------------------------------------------------------------
/stories/Counter/_Counter.tsx:
--------------------------------------------------------------------------------
1 | import { DemoTab } from 'index';
2 |
3 | import Demo from './Counter';
4 |
5 | const code = `import { useState } from 'react';
6 |
7 | const Counter = () => {
8 | const [count, setCount] = useState(0);
9 |
10 | const onCountInc = () => setCount(count + 1);
11 |
12 | const msg = \`Button clicked \${count.toString()} \${count > 1 ? 'times' : 'time'}\`;
13 | const marginBottom = 10;
14 |
15 | return (
16 | <>
17 |
23 | {msg}
24 | >
25 | );
26 | };
27 |
28 | export default Counter;`;
29 |
30 | export const _Counter = () => (
31 |
32 |
33 |
34 | );
35 |
--------------------------------------------------------------------------------
/stories/Default/Default.jsx:
--------------------------------------------------------------------------------
1 | const Default = () => DemoTab 📑
;
2 |
3 | export default Default;
4 |
--------------------------------------------------------------------------------
/stories/Default/_Default.tsx:
--------------------------------------------------------------------------------
1 | import { DemoTab } from 'index';
2 |
3 | import Demo from './Default';
4 |
5 | const code = `const Default = () => DemoTab 📑
;
6 |
7 | export default Default;`;
8 |
9 | export const _Default = () => (
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/stories/DemoTab.scss:
--------------------------------------------------------------------------------
1 | #root {
2 | font-family: Arial, sans-serif;
3 |
4 | .tab-demo.react-tabs__tab-panel--selected {
5 | margin-top: 10vh;
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/stories/DemoTab.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta } from '@storybook/react';
2 |
3 | import { _ButtonGreen } from './ButtonGreen/_ButtonGreen';
4 | import { _ButtonRed } from './ButtonRed/_ButtonRed';
5 | import { _Counter } from './Counter/_Counter';
6 | import { _Default } from './Default/_Default';
7 |
8 | import './DemoTab.scss';
9 |
10 | const meta: Meta = {
11 | title: 'Examples',
12 | };
13 |
14 | export default meta;
15 |
16 | export const Default = () => <_Default />;
17 |
18 | export const ButtonGreen = () => <_ButtonGreen />;
19 | ButtonGreen.storyName = 'Button Green - css';
20 |
21 | export const ButtonRed = () => <_ButtonRed />;
22 | ButtonRed.storyName = 'Button Red - scss';
23 |
24 | export const Counter = () => <_Counter />;
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./scripts/tsconfig.dev.json"
3 | }
4 |
--------------------------------------------------------------------------------