Commit efb5f708 by Syed Abdul Rahman

implementing components in story book

parent a22c9d37
...@@ -22,3 +22,6 @@ dist-ssr ...@@ -22,3 +22,6 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
*storybook.log
storybook-static
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
"stories": [
"../src/**/*.mdx",
"../src/components/**/*.stories.@(js|jsx|ts|tsx)",
],
"addons": [
"@storybook/addon-onboarding",
"@chromatic-com/storybook",
"@storybook/addon-docs",
"@storybook/addon-a11y",
"@storybook/addon-vitest"
],
"framework": {
"name": "@storybook/react-vite",
"options": {}
}
};
export default config;
\ No newline at end of file
/** @type { import('@storybook/react-vite').Preview } */
import '../src/assets/fonts.css'
const preview = {
parameters: {
backgrounds: {
options: {
// Define your custom background colors
light: { name: 'Light', value: '#F7F9F2' },
dark: { name: 'Dark', value: '#333' },
maroon: { name: 'Maroon', value: '#400' },
myCustomBlue: { name: 'My Custom Blue', value: '#3444c5' }, // Example
},
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: "todo"
}
},
};
export default preview;
\ No newline at end of file
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { setProjectAnnotations } from '@storybook/react-vite';
import * as projectAnnotations from './preview';
// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
// .storybook/preview.js
export const parameters = {
backgrounds: {
default: 'custom',
values: [
{
name: 'custom',
value: 'red', // 👈 Your desired canvas background color
},
],
},
};
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";
import js from '@eslint/js' import js from '@eslint/js'
import globals from 'globals' import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from 'eslint-plugin-react-refresh'
export default [ export default [{ ignores: ['dist'] }, {
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'], files: ['**/*.{js,jsx}'],
languageOptions: { languageOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
...@@ -29,5 +30,4 @@ export default [ ...@@ -29,5 +30,4 @@ export default [
{ allowConstantExport: true }, { allowConstantExport: true },
], ],
}, },
}, }, ...storybook.configs["flat/recommended"]];
]
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -7,21 +7,36 @@ ...@@ -7,21 +7,36 @@
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}, },
"dependencies": { "dependencies": {
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0" "react-dom": "^19.1.0"
}, },
"devDependencies": { "devDependencies": {
"@chromatic-com/storybook": "^4.0.0",
"@eslint/js": "^9.25.0", "@eslint/js": "^9.25.0",
"@storybook/addon-a11y": "^9.0.4",
"@storybook/addon-docs": "^9.0.4",
"@storybook/addon-onboarding": "^9.0.4",
"@storybook/addon-vitest": "^9.0.4",
"@storybook/react-vite": "^9.0.4",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2", "@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1", "@vitejs/plugin-react": "^4.4.1",
"@vitest/browser": "^3.2.0",
"@vitest/coverage-v8": "^3.2.0",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-react-refresh": "^0.4.19",
"eslint-plugin-storybook": "^9.0.4",
"globals": "^16.0.0", "globals": "^16.0.0",
"vite": "^6.3.5" "playwright": "^1.52.0",
"prop-types": "^15.8.1",
"storybook": "^9.0.4",
"vite": "^6.3.5",
"vitest": "^3.2.0"
} }
} }
import './App.css' import './App.css'
import Button from './components/Base/Button/Index'
function App() { function App() {
return ( return (
<> <>
<h3> Booking APP</h3> <h3> Booking APP</h3>
<Button>Confirm</Button>
</> </>
) )
} }
......
@font-face {
font-family: 'Poppins-Medium';
src: url('../assets/fonts/Poppins-Medium.ttf');
}
@font-face {
font-family: 'Poppins-Bold';
src: url('../assets/fonts/Poppins-Bold.ttf');
}
\ No newline at end of file
import Button from './Index';
export default {
title: 'Component/Base/Button',
tags: ['autodocs'],
component: Button
}
export const Primary = {
args: {
primary: true,
label: 'Button',
},
render: () => {
return <Button>Confirm</Button>
}
};
\ No newline at end of file
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styles from './styles.module.css'; import styles from './styles.module.css';
const Button = (props) => { const Button = ({ loading, size, disabled, children, ...props }) => {
const {
title,
onConfirm
} = props;
return ( return (
<button className={styles.button} onClick={onConfirm}>{title}</button> <button className={styles.button} {...props}>{children}</button>
) )
} }
...@@ -18,7 +12,9 @@ export default Button; ...@@ -18,7 +12,9 @@ export default Button;
Button.PropTypes = { Button.PropTypes = {
title: PropTypes.string.isRequired, children: PropTypes.node.isRequired,
onConfirm: PropTypes.func.isRequired, disabled: PropTypes.bool,
onClick: PropTypes.func,
size: PropTypes.string,
loading: PropTypes.bool
} }
\ No newline at end of file
...@@ -3,4 +3,9 @@ ...@@ -3,4 +3,9 @@
outline: none; outline: none;
background-color: white; background-color: white;
color: black; color: black;
border-radius: 5px;
padding: 0.5rem 1.5rem;
font-family: 'Poppins-Bold';
cursor: pointer;
font-size: 18px;
} }
\ No newline at end of file
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const Input = (props) => { const Input = ({ ...inputPprops }) => {
const { onValueChange } = props;
return ( return (
<input onChange={onValueChange} /> <input {...inputPprops} />
) )
} }
...@@ -13,5 +12,8 @@ export default Input; ...@@ -13,5 +12,8 @@ export default Input;
Input.PropTypes = { Input.PropTypes = {
onValueChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired,
type: PropTypes.string,
value: PropTypes.string.isRequired,
placeholder: PropTypes.string
} }
\ No newline at end of file
import styles from './styles.module.css'; import styles from './styles.module.css';
import PropTypes from 'prop-types';
const Legend = (props) => { const Legend = ({ children, type="selected" }) => {
const { title, type } = props;
return ( return (
<div>Legend</div> <div className={styles['legend-wrapper']}>
<div className={styles[type]}></div>
<div className={styles.title}>{children}</div>
</div>
) )
} }
Legend.PropTypes = {
children: PropTypes.node.isRequired,
type: PropTypes.oneOf(["selected", "reserved", "available"])
}
export default Legend; export default Legend;
import Legend from "./Index"
export default {
title: "Component/Base/Legend",
tags: ["autodocs"],
component: Legend
}
export const Selected = {
args: {
type: 'selected'
},
render: (args) => {
return <Legend type={args.type}>Selected</Legend>
}
};
export const Reserved = {
args: {
type: 'reserved'
},
render: (args) => {
return <Legend type={args.type}>Reserved</Legend>
}
};
export const Available = {
args: {
type: 'available'
},
render: (args) => {
return <Legend type={args.type}>available</Legend>
}
};
\ No newline at end of file
.selected {
width: 15px;
height: 15px;
border-radius: 50%;
background-color: aqua;
}
.reserved {
width: 15px;
height: 15px;
border-radius: 50%;
background-color: rgb(151, 155, 155);
}
.available {
width: 15px;
height: 15px;
border-radius: 50%;
border: 1px solid white;
}
.legend-wrapper {
display: flex;
gap: 10px;
align-items: center;
}
.title {
color: white;
font-family: 'Poppins-Medium';
}
\ No newline at end of file
import BookingWrapper from "./Index";
export default {
title: 'Component/Layout/BookingWrapper',
tags: ["autodocs"],
component: BookingWrapper
}
export const Default = {
}
\ No newline at end of file
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const BookingWrapper = (props) => { const BookingWrapper = () => {
const aisleIndex = 4;
const seatData = Array.from({ length: 10 }, (_, index) => ({
id: index + 1,
status: 'available'
}));
const { seatData } = props; const isLeftBlock = (col) => col < aisleIndex;
const isRightBlock = (col) => col > aisleIndex;
return ( return (
<div> <div>
<div className='theatre'> <div className='theatre'>
{Array.from({ length: seatData.row }, (_, row_index) => ( {Array.from({ length: 5 }, (_, row_index) => (
<div className='seat-row'> <div className='seat-row'>
{Array.from({ length: seatData.colum }, (_, column_index) => { {seatData.map((_, column_index) => {
const isFirstRow = row_index === 0; const isFirstRow = row_index === 0;
const isLastRow = row_index === seatData.row - 1; const isLastRow = row_index === seatData.row - 1;
...@@ -19,12 +25,12 @@ const BookingWrapper = (props) => { ...@@ -19,12 +25,12 @@ const BookingWrapper = (props) => {
if ( if (
isRightBlock(column_index) && isRightBlock(column_index) &&
column_index === seatData.colum - 1 && column_index === seatData.id - 1 &&
(isFirstRow || isLastRow) (isFirstRow || isLastRow)
) { ) {
return <div key={`gap-right-${row_index}-${row_index}`} className="seat-gap" />; return <div key={`gap-right-${row_index}-${row_index}`} className="seat-gap" />;
} }
if (column_index == seatData.aisleIndex) { if (column_index == aisleIndex) {
return ( return (
<div className='aisle'></div> <div className='aisle'></div>
) )
...@@ -43,10 +49,3 @@ const BookingWrapper = (props) => { ...@@ -43,10 +49,3 @@ const BookingWrapper = (props) => {
export default BookingWrapper; export default BookingWrapper;
BookingWrapper.PropTypes = {
seatData: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
status: PropTypes.oneOf(['available', 'reserved', 'selected']).isRequired,
})).isRequired
}
\ No newline at end of file
import styles from './styles.module.css' import styles from './styles.module.css'
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const Seat = (props) => { const Seat = ({ status, ...rest }) => {
const {onSeatClick, status} = props
return (
<div onClick={onSeatClick} className={` ${styles[status]} ${styles.seat} `}>
</div> return (
<div {...rest} className={` ${styles[status]} ${styles.seat} `}></div>
) )
} }
Seat.PropTypes = { Seat.PropTypes = {
onSeatClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
status: PropTypes.oneOf(['available', 'reserved', 'selected']) status: PropTypes.oneOf(['available', 'reserved', 'selected'])
} }
......
import Seat from './Seat';
export default {
tags: ["autodocs"],
title: "Component/Layout/BookingWrapper/Seat",
component: Seat
}
export const Available = {
args: {
},
render: () => {
return <Seat status={"available"}/>
}
}
export const Selected = {
args: {
},
render: () => {
return <Seat status={"selected"}/>
}
}
export const Reserved = {
args: {
},
render: () => {
return <Seat status={"reserved"}/>
}
}
\ No newline at end of file
...@@ -5,3 +5,11 @@ ...@@ -5,3 +5,11 @@
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
} }
.reserved{
background-color: gray;
}
.selected{
background-color: aqua;
}
\ No newline at end of file
import Header from './Index'
export default {
title: "Component/Layout/Header",
tags: ["autodocs"],
component: Header
}
export const Default = {
args: {
},
render: () => {
return <Header>Choose Seats</Header>
}
}
\ No newline at end of file
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styles from './styles.module.css';
const Header = (props) => { const Header = ({ children }) => {
const { title } = props;
return ( return (
<div>{title}</div> <section className={styles['header-wrapper']}>
<div className={styles.title}>
{children}
</div>
<div style={{width: '60%'}}>
<svg viewBox="0 0 480 260" xmlns="http://www.w3.org/2000/svg">
<path d="M 30 70 Q 240 20 450 70" stroke="white" stroke-width="5" fill="none" />
<defs>
<linearGradient id="glowGradient" x1="0" y1="70" x2="0" y2="180" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#addfff" stop-opacity="0.15" />
<stop offset="100%" stop-color="#addfff" stop-opacity="0" />
</linearGradient>
<clipPath id="glowClip">
<path d="
M 30 70
Q 240 20 450 70
L 470 180
L 10 180
Z" />
</clipPath>
</defs>
<rect x="0" y="0" width="480" height="260"
fill="url(#glowGradient)"
clip-path="url(#glowClip)" />
</svg>
</div>
</section>
) )
} }
Header.PropTypes = { Header.PropTypes = {
title: PropTypes.string.isRequired children: PropTypes.node.isRequired
} }
export default Header export default Header
\ No newline at end of file
.title{
font-family: 'Poppins-Bold';
color: white;
font-size: 24px;
}
.header-wrapper{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
}
\ No newline at end of file
import styles from './styles.module.css'; import styles from './styles.module.css';
import PropTypes from 'prop-types'; import Legend from '../../Base/Legend/Index'
const LegendWrapper = (props) => { const LegendWrapper = (props) => {
const { const legendData = [
legendData {
} = props; id: 1,
label: "Selected",
status: 'selected'
},
{
id: 2,
label: "Reserved",
status: "reserved"
},
{
id: 3,
label: "Available",
status: "available"
}
]
return ( return (
<div>LegendWrapper</div> <div className={styles.LegendWrapper}>
{legendData.map((ele) => (
<Legend type={ele.status}>{ele.label}</Legend>
))}
</div>
) )
} }
export default LegendWrapper; export default LegendWrapper;
LegendWrapper.PropTypes = {
legendData: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
status: PropTypes.oneOf(['available', 'reserved', 'selected']).isRequired,
})
)
}
\ No newline at end of file
import LegendWrapper from "./Index";
export default {
title: "Component/TopLevel/LegendWrapper",
tags: ["autodocs"],
component: LegendWrapper
}
export const Default = {
}
\ No newline at end of file
.LegendWrapper {
display: flex;
gap: 1rem;
}
\ No newline at end of file
@import '../src//assets//fonts.css';
*{ *{
padding: 0; padding: 0;
margin: 0; margin: 0;
......
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
const dirname =
typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineWorkspace([
'vite.config.js',
{
extends: 'vite.config.js',
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({ configDir: path.join(dirname, '.storybook') }),
],
test: {
name: 'storybook',
browser: {
enabled: true,
headless: true,
provider: 'playwright',
instances: [{ browser: 'chromium' }]
},
setupFiles: ['.storybook/vitest.setup.js'],
},
},
]);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment