diff --git a/libs/react/src/App.css b/libs/react/src/App.css index b9d355df..87ef48d5 100644 --- a/libs/react/src/App.css +++ b/libs/react/src/App.css @@ -1,42 +1,30 @@ #root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center + } .logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms + } @keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - + from { + transform: rotate(0deg); + } to { + transform: rotate(360deg); + } + } @media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear + } + } .card { - padding: 2em; -} - + padding: 2em + } .read-the-docs { - color: #888; -} + color: #888 + } \ No newline at end of file diff --git a/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css b/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css new file mode 100644 index 00000000..0145e1da --- /dev/null +++ b/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css @@ -0,0 +1,44 @@ +.image-viewer { + width: var(--viewer-width, 100%); + height: var(--viewer-height, 400px); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + position: relative + } +.image-viewer img { + width: 100%; + height: auto; + transition: transform 0.3s ease + } +.zoomed img { + transform: scale(var(--zoom-scale, 1.5)) + } +.loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-weight: var(--loading-font-weight, bold) + } +@media (max-width: 600px) { + .image-viewer { + height: var(--viewer-height-mobile, 300px) + } + } +@media (min-width: 601px) and (max-width: 768px) { + .image-viewer { + height: var(--viewer-height-tablet, 350px) + } + } +@media (min-width: 769px) and (max-width: 1024px) { + .image-viewer { + height: var(--viewer-height-large-tablet, 400px) + } + } +@media (min-width: 1025px) { + .image-viewer { + height: var(--viewer-height-desktop, 500px) + } + } diff --git a/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.tsx b/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.tsx new file mode 100644 index 00000000..e90384eb --- /dev/null +++ b/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import DegreeImageViewer, { ImageViewerProps } from './360-DegreeImageViewer'; + +export default { + title: 'component/Media/360-DegreeImageViewer', + component: DegreeImageViewer, + tags: ['autodocs'], +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + images: Array.from({ length: 36 }, (_, i) => `/images/frame${i + 1}.jpg`), + isRotating: false, + isZoomed: false, + isLoading: false, +}; + +export const Rotating = Template.bind({}); +Rotating.args = { + ...Default.args, + isRotating: true, +}; + +export const Paused = Template.bind({}); +Paused.args = { + ...Default.args, + isRotating: false, +}; + +export const ZoomInOut = Template.bind({}); +ZoomInOut.args = { + ...Default.args, + isZoomed: true, +}; + +export const Loading = Template.bind({}); +Loading.args = { + ...Default.args, + isLoading: true, +}; \ No newline at end of file diff --git a/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.tsx b/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.tsx new file mode 100644 index 00000000..0fd00443 --- /dev/null +++ b/libs/react/src/components/360-DegreeImageViewer/360-DegreeImageViewer.tsx @@ -0,0 +1,35 @@ +import React, { useState, useEffect } from 'react'; + +export interface ImageViewerProps { + images: string[]; + isRotating: boolean; + isZoomed: boolean; + isLoading: boolean; +} + +const DegreeImageViewer: React.FC = ({ images, isRotating, isZoomed, isLoading }) => { + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + if (isRotating) { + const interval = setInterval(() => { + setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length); + }, 100); + return () => clearInterval(interval); + } + }, [isRotating, images.length]); + + const zoomClass = isZoomed ? 'zoomed' : ''; + + return ( +
+ {isLoading ? ( +
Loading...
+ ) : ( + {`Frame + )} +
+ ); +}; + +export default DegreeImageViewer; \ No newline at end of file diff --git a/libs/react/src/components/Accordion/Accordion.css b/libs/react/src/components/Accordion/Accordion.css new file mode 100644 index 00000000..38ff1ad0 --- /dev/null +++ b/libs/react/src/components/Accordion/Accordion.css @@ -0,0 +1,51 @@ +.accordion-title { + width: 100%; + background: var(--accordion-bg, #f7f7f7); + padding: var(--accordion-padding, 10px); + text-align: left; + cursor: pointer; + transition: background 0.3s ease; + border: none; + outline: none; + font-size: var(--accordion-font-size, 16px) + } +.accordion-content { + background: var(--accordion-content-bg, #fff); + padding: var(--accordion-content-padding, 10px); + font-size: var(--accordion-content-font-size, 14px) + } +.accordion-title:hover { + background: var(--accordion-hover-bg, #e2e2e2) + } +@media (max-width: 600px) { + .accordion-title { + font-size: 14px + } + .accordion-content { + font-size: 12px + } + } +@media (min-width: 601px) and (max-width: 768px) { + .accordion-title { + font-size: 15px + } + .accordion-content { + font-size: 13px + } + } +@media (min-width: 769px) and (max-width: 1024px) { + .accordion-title { + font-size: 16px + } + .accordion-content { + font-size: 14px + } + } +@media (min-width: 1025px) { + .accordion-title { + font-size: 17px + } + .accordion-content { + font-size: 15px + } + } \ No newline at end of file diff --git a/libs/react/src/components/Accordion/Accordion.stories.tsx b/libs/react/src/components/Accordion/Accordion.stories.tsx new file mode 100644 index 00000000..c93e381f --- /dev/null +++ b/libs/react/src/components/Accordion/Accordion.stories.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import Accordion, { AccordionProps } from './Accordion'; + +export default { + title: 'component/Lists/Accordion', + component: Accordion, + tags: ['autodocs'], +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + title: 'Accordion Title', + content: 'This is the accordion content.', + isOpen: false, +}; + +export const Open = Template.bind({}); +Open.args = { + title: 'Accordion Title', + content: 'This is the accordion content.', + isOpen: true, +}; + +export const Closed = Template.bind({}); +Closed.args = { + title: 'Accordion Title', + content: 'This is the accordion content.', + isOpen: false, +}; + +export const Hover = Template.bind({}); +Hover.args = { + title: 'Accordion Title', + content: 'This is the accordion content.', + isOpen: false, +}; \ No newline at end of file diff --git a/libs/react/src/components/Accordion/Accordion.tsx b/libs/react/src/components/Accordion/Accordion.tsx new file mode 100644 index 00000000..d72f239c --- /dev/null +++ b/libs/react/src/components/Accordion/Accordion.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; + +export interface AccordionProps { + title: string; + content: string; + isOpen?: boolean; +} + +const Accordion: React.FC = ({ title, content, isOpen = false }) => { + const [open, setOpen] = useState(isOpen); + + const toggleAccordion = () => { + setOpen(!open); + }; + + return ( +
+ + {open && ( +
+ {content} +
+ )} +
+ ); +}; + +export default Accordion; \ No newline at end of file diff --git a/libs/react/src/components/ActionSheet/ActionSheet.css b/libs/react/src/components/ActionSheet/ActionSheet.css new file mode 100644 index 00000000..e0564441 --- /dev/null +++ b/libs/react/src/components/ActionSheet/ActionSheet.css @@ -0,0 +1,60 @@ +.action-sheet { + position: fixed; + bottom: 0; + left: 0; + right: 0; + transition: transform 0.3s ease; + background: var(--action-sheet-bg, #fff); + z-index: 1000 + } +.action-sheet.open { + transform: translatey(0) + } +.action-sheet.closed { + transform: translatey(100%) + } +.action-sheet-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--action-sheet-backdrop-bg, rgba(0, 0, 0, 0.5)) + } +.action-sheet-content { + padding: var(--action-sheet-content-padding, 16px) + } +.action-list { + list-style: none; + padding: 0; + margin: 0 + } +.action-item { + margin-bottom: var(--action-item-margin-bottom, 8px) + } +.action-button { + width: 100%; + padding: var(--action-button-padding, 12px); + background: var(--action-button-bg, #f7f7f7); + border: none; + cursor: pointer; + transition: background-color 0.3s ease + } +.action-button:hover { + background: var(--action-button-hover-bg, #e0e0e0) + } +@media (max-width: 576px) { + .action-sheet-content { + padding: var(--action-sheet-content-padding-sm, 12px) + } + } +@media (min-width: 577px) and (max-width: 768px) { + .action-sheet-content { + padding: var(--action-sheet-content-padding-md, 14px) + } + } +@media (min-width: 769px) { + .action-sheet-content { + padding: var(--action-sheet-content-padding-lg, 16px) + } + } \ No newline at end of file diff --git a/libs/react/src/components/ActionSheet/ActionSheet.stories.tsx b/libs/react/src/components/ActionSheet/ActionSheet.stories.tsx new file mode 100644 index 00000000..ad76dbd8 --- /dev/null +++ b/libs/react/src/components/ActionSheet/ActionSheet.stories.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import ActionSheet, { ActionSheetProps } from './ActionSheet'; + +export default { + title: 'component/Overlays/ActionSheet', + component: ActionSheet, + tags: ['autodocs'], +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + isOpen: true, + onClose: () => console.log('Closed'), + actions: [ + { label: 'Action 1', onClick: () => console.log('Action 1 clicked') }, + { label: 'Action 2', onClick: () => console.log('Action 2 clicked') }, + ], +}; + +export const Opened = Template.bind({}); +Opened.args = { + isOpen: true, + onClose: () => console.log('Closed'), + actions: [ + { label: 'Action 1', onClick: () => console.log('Action 1 clicked') }, + { label: 'Action 2', onClick: () => console.log('Action 2 clicked') }, + ], +}; + +export const Closed = Template.bind({}); +Closed.args = { + isOpen: false, + onClose: () => console.log('Closed'), + actions: [ + { label: 'Action 1', onClick: () => console.log('Action 1 clicked') }, + { label: 'Action 2', onClick: () => console.log('Action 2 clicked') }, + ], +}; + +export const Hover = Template.bind({}); +Hover.args = { + isOpen: true, + onClose: () => console.log('Closed'), + actions: [ + { label: 'Action 1', onClick: () => console.log('Action 1 clicked') }, + { label: 'Action 2', onClick: () => console.log('Action 2 clicked') }, + ], +}; \ No newline at end of file diff --git a/libs/react/src/components/ActionSheet/ActionSheet.tsx b/libs/react/src/components/ActionSheet/ActionSheet.tsx new file mode 100644 index 00000000..b1fa58f6 --- /dev/null +++ b/libs/react/src/components/ActionSheet/ActionSheet.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +export interface ActionSheetProps { + isOpen: boolean; + onClose: () => void; + actions: { label: string; onClick: () => void }[]; +} + +const ActionSheet: React.FC = ({ isOpen, onClose, actions }) => { + return ( +
+
+
+
    + {actions.map((action, index) => ( +
  • + +
  • + ))} +
+
+
+ ); +}; + +export default ActionSheet; \ No newline at end of file diff --git a/libs/react/src/components/ActionableList/ActionableList.css b/libs/react/src/components/ActionableList/ActionableList.css new file mode 100644 index 00000000..61ca2d32 --- /dev/null +++ b/libs/react/src/components/ActionableList/ActionableList.css @@ -0,0 +1,52 @@ +.actionable-list { + list-style-type: none; + padding: 0; + margin: 0 + } +.actionable-list-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--list-item-padding, 10px) + } +.item-text { + font-size: var(--list-item-font-size, 16px) + } +.item-action { + background: var(--action-button-bg, #007bff); + color: var(--action-button-color, #fff); + border: none; + cursor: pointer; + transition: background 0.3s ease + } +.item-action:hover { + background: var(--action-button-hover-bg, #0056b3) + } +.item-action:disabled { + background: var(--action-button-disabled-bg, #ccc); + cursor: not-allowed + } +.disabled .item-action { + background: var(--action-button-disabled-bg, #ccc); + cursor: not-allowed + } +@media (max-width: 600px) { + .item-text { + font-size: 14px + } + } +@media (min-width: 601px) and (max-width: 768px) { + .item-text { + font-size: 15px + } + } +@media (min-width: 769px) and (max-width: 1024px) { + .item-text { + font-size: 16px + } + } +@media (min-width: 1025px) { + .item-text { + font-size: 17px + } + } \ No newline at end of file diff --git a/libs/react/src/components/ActionableList/ActionableList.stories.tsx b/libs/react/src/components/ActionableList/ActionableList.stories.tsx new file mode 100644 index 00000000..94e0b52d --- /dev/null +++ b/libs/react/src/components/ActionableList/ActionableList.stories.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import ActionableList,{ActionableListProps} from './ActionableList'; + +export default { + title: 'component/Lists/ActionableList', + component: ActionableList, + tags: ['autodocs'], +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: 1, text: 'Item 1', actionLabel: 'Action' }, + { id: 2, text: 'Item 2', actionLabel: 'Action' }, + ], + onAction: (id) => alert(`Action triggered for item ${id}`), +}; + +export const Hover = Template.bind({}); +Hover.args = { + ...Default.args, +}; + +export const ActionTriggered = Template.bind({}); +ActionTriggered.args = { + ...Default.args, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items: [ + { id: 1, text: 'Item 1', actionLabel: 'Action', disabled: true }, + { id: 2, text: 'Item 2', actionLabel: 'Action', disabled: true }, + ], + onAction: (id) => alert(`Action triggered for item ${id}`), +}; + +export const Loading = Template.bind({}); +Loading.args = { + ...Default.args, + isLoading: true, +}; \ No newline at end of file diff --git a/libs/react/src/components/ActionableList/ActionableList.tsx b/libs/react/src/components/ActionableList/ActionableList.tsx new file mode 100644 index 00000000..6017ff2d --- /dev/null +++ b/libs/react/src/components/ActionableList/ActionableList.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +interface ActionableListItem { + id: number; + text: string; + actionLabel: string; + disabled?: boolean; +} + +export interface ActionableListProps { + items: ActionableListItem[]; + onAction: (id: number) => void; + isLoading?: boolean; +} + +const ActionableList: React.FC = ({ items, onAction, isLoading = false }) => { + return ( +
    + {items.map((item) => ( +
  • + {item.text} + +
  • + ))} +
+ ); +}; + +export default ActionableList; \ No newline at end of file diff --git a/libs/react/src/components/ActivityIndicators/ActivityIndicators.css b/libs/react/src/components/ActivityIndicators/ActivityIndicators.css new file mode 100644 index 00000000..2e4cbf4c --- /dev/null +++ b/libs/react/src/components/ActivityIndicators/ActivityIndicators.css @@ -0,0 +1,46 @@ +.activity-indicator { + display: flex; + align-items: center; + justify-content: center; + padding: 10px; + border-radius: 50%; + width: 50px; + height: 50px; + transition: background-color 0.3s + } +.loading { + background-color: var(--loading-background-color); + color: var(--loading-text-color) + } +.success { + background-color: var(--success-background-color); + color: var(--success-text-color) + } +.error { + background-color: var(--error-background-color); + color: var(--error-text-color) + } +@media (max-width: 600px) { + .activity-indicator { + width: 40px; + height: 40px + } + } +@media (min-width: 601px) and (max-width: 768px) { + .activity-indicator { + width: 45px; + height: 45px + } + } +@media (min-width: 769px) and (max-width: 1024px) { + .activity-indicator { + width: 50px; + height: 50px + } + } +@media (min-width: 1025px) { + .activity-indicator { + width: 50px; + height: 50px + } + } \ No newline at end of file diff --git a/libs/react/src/components/ActivityIndicators/ActivityIndicators.stories.tsx b/libs/react/src/components/ActivityIndicators/ActivityIndicators.stories.tsx new file mode 100644 index 00000000..b09daddb --- /dev/null +++ b/libs/react/src/components/ActivityIndicators/ActivityIndicators.stories.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import ActivityIndicators, { ActivityIndicatorsProps } from './ActivityIndicators'; + +export default { + title: 'component/Indicators/ActivityIndicators', + component: ActivityIndicators, + tags: ['autodocs'], +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Loading = Template.bind({}); +Loading.args = { + state: 'loading', +}; + +export const Success = Template.bind({}); +Success.args = { + state: 'success', +}; + +export const Error = Template.bind({}); +Error.args = { + state: 'error', +}; \ No newline at end of file diff --git a/libs/react/src/components/ActivityIndicators/ActivityIndicators.tsx b/libs/react/src/components/ActivityIndicators/ActivityIndicators.tsx new file mode 100644 index 00000000..adffb94a --- /dev/null +++ b/libs/react/src/components/ActivityIndicators/ActivityIndicators.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +export interface ActivityIndicatorsProps { + state: 'loading' | 'success' | 'error'; +} + +const ActivityIndicators: React.FC = ({ state }) => { + return ( +
+ {state === 'loading' && Loading...} + {state === 'success' && Success!} + {state === 'error' && Error!} +
+ ); +}; + +export default ActivityIndicators; \ No newline at end of file diff --git a/libs/react/src/components/AddMemberButton/AddMemberButton.css b/libs/react/src/components/AddMemberButton/AddMemberButton.css new file mode 100644 index 00000000..ac7065c8 --- /dev/null +++ b/libs/react/src/components/AddMemberButton/AddMemberButton.css @@ -0,0 +1,33 @@ +.add-member-button { + font-size: var(--button-font-size, 16px); + background-color: var(--button-bg-color, #4caf50); + color: var(--button-text-color, #fff); + border: none; + cursor: pointer; + transition: background-color 0.3s ease + } +.add-member-button.disabled { + background-color: var(--button-disabled-bg-color, #ccc); + cursor: not-allowed + } +.add-member-button:hover:enabled { + background-color: var(--button-hover-bg-color, #45a049) + } +.add-member-button:active:enabled { + background-color: var(--button-active-bg-color, #388e3c) + } +@media (max-width: 576px) { + .add-member-button { + font-size: var(--button-font-size-sm, 14px) + } + } +@media (min-width: 577px) and (max-width: 768px) { + .add-member-button { + font-size: var(--button-font-size-md, 16px) + } + } +@media (min-width: 769px) { + .add-member-button { + font-size: var(--button-font-size-lg, 18px) + } + } \ No newline at end of file diff --git a/libs/react/src/components/AddMemberButton/AddMemberButton.stories.tsx b/libs/react/src/components/AddMemberButton/AddMemberButton.stories.tsx new file mode 100644 index 00000000..d45d4aae --- /dev/null +++ b/libs/react/src/components/AddMemberButton/AddMemberButton.stories.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import AddMemberButton, { AddMemberButtonProps } from './AddMemberButton'; + +export default { + title: 'component/Chat/AddMemberButton', + component: AddMemberButton, + tags: ['autodocs'], +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + isEnabled: true, + onClick: () => console.log('Button clicked'), +}; + +export const Enabled = Template.bind({}); +Enabled.args = { + ...Default.args, + isEnabled: true, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + ...Default.args, + isEnabled: false, +}; + +export const Hovered = Template.bind({}); +Hovered.args = { + ...Default.args, + isEnabled: true, +}; + +export const Clicked = Template.bind({}); +Clicked.args = { + ...Default.args, + isEnabled: true, +}; \ No newline at end of file diff --git a/libs/react/src/components/AddMemberButton/AddMemberButton.tsx b/libs/react/src/components/AddMemberButton/AddMemberButton.tsx new file mode 100644 index 00000000..6cc8a9ed --- /dev/null +++ b/libs/react/src/components/AddMemberButton/AddMemberButton.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +export interface AddMemberButtonProps { + isEnabled: boolean; + onClick: () => void; +} + +const AddMemberButton: React.FC = ({ isEnabled, onClick }) => { + return ( + + ); +}; + +export default AddMemberButton; \ No newline at end of file diff --git a/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.css b/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.css new file mode 100644 index 00000000..36c0d947 --- /dev/null +++ b/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.css @@ -0,0 +1,70 @@ +.scheduler-container { + max-width: 800px; + margin: auto; + padding: var(--scheduler-padding, 20px); + background-color: var(--scheduler-bg, #f9f9f9); + border-radius: var(--scheduler-border-radius, 8px) + } +.event-form { + display: flex; + flex-direction: column; + gap: var(--form-gap, 10px); + margin-bottom: var(--form-margin-bottom, 20px) + } +.event-form input, .event-form textarea { + padding: var(--form-input-padding, 10px); + border: 1px solid var(--form-border, #ccc); + border-radius: var(--form-border-radius, 4px) + } +.event-form button { + background-color: var(--button-bg, #28a745); + color: var(--button-color, #fff); + border: none; + padding: var(--button-padding, 10px); + border-radius: var(--button-border-radius, 4px); + cursor: pointer; + transition: background-color 0.3s + } +.event-form button:hover { + background-color: var(--button-hover-bg, #218838) + } +.events-list { + display: flex; + flex-direction: column; + gap: var(--event-gap, 10px) + } +.event-item { + padding: var(--event-item-padding, 10px); + background-color: var(--event-item-bg, #fff); + border: 1px solid var(--event-item-border, #e0e0e0); + border-radius: var(--event-item-border-radius, 4px); + cursor: pointer; + transition: background-color 0.3s + } +.event-item:hover { + background-color: var(--event-item-hover-bg, #e6f7ff) + } +.event-details { + margin-top: var(--event-details-margin-top, 20px); + padding: var(--event-details-padding, 15px); + background-color: var(--event-details-bg, #fff); + border-radius: var(--event-details-border-radius, 4px) + } +.event-details button { + margin-right: var(--button-margin-right, 10px); + background-color: var(--button-bg, #dc3545); + color: var(--button-color, #fff); + border: none; + padding: var(--button-padding, 10px); + border-radius: var(--button-border-radius, 4px); + cursor: pointer; + transition: background-color 0.3s + } +.event-details button:hover { + background-color: var(--button-hover-bg, #c82333) + } +@media (max-width: 600px) { + .scheduler-container { + padding: var(--scheduler-padding-mobile, 10px) + } + } \ No newline at end of file diff --git a/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.stories.tsx b/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.stories.tsx new file mode 100644 index 00000000..6beb0826 --- /dev/null +++ b/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.stories.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import AdminViewScheduler, { AdminViewSchedulerProps } from './AdminViewScheduler'; + +export default { + title: 'component/Scheduling/AdminViewScheduler', + component: AdminViewScheduler, + tags: ['autodocs'], +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + initialEvents: [ + { + id: '1', + title: 'Team Meeting', + category: 'Work', + location: 'Conference Room', + time: '9:00 AM - 10:00 AM', + description: 'Monthly team meeting.', + }, + { + id: '2', + title: 'Project Deadline', + category: 'Deadline', + location: 'Office', + time: 'All Day', + description: 'Final project submission date.', + }, + ], +}; + +export const EventAdded = Template.bind({}); +EventAdded.args = { + initialEvents: [...(Default.args?.initialEvents || [])], +}; + +export const EventEdited = Template.bind({}); +EventEdited.args = { + initialEvents: [ + ...(Default.args?.initialEvents || []).map(event => + event.id === '1' ? { ...event, title: 'Updated Team Meeting' } : event + ), + ], +}; + +export const EventDeleted = Template.bind({}); +EventDeleted.args = { + initialEvents: (Default.args?.initialEvents || []).filter(event => event.id !== '1'), +}; \ No newline at end of file diff --git a/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.tsx b/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.tsx new file mode 100644 index 00000000..f7eb12be --- /dev/null +++ b/libs/react/src/components/AdminViewScheduler/AdminViewScheduler.tsx @@ -0,0 +1,100 @@ +import React, { useState } from 'react'; +import './AdminViewScheduler.css'; + +interface Event { + id: string; + title: string; + category: string; + location: string; + time: string; + description: string; +} + +export interface AdminViewSchedulerProps { + initialEvents: Event[]; +} + +const AdminViewScheduler: React.FC = ({ initialEvents }) => { + const [events, setEvents] = useState(initialEvents); + const [selectedEvent, setSelectedEvent] = useState(null); + const [newEvent, setNewEvent] = useState({ + id: '', + title: '', + category: '', + location: '', + time: '', + description: '', + }); + + const handleAddEvent = () => { + setEvents([...events, { ...newEvent, id: String(Date.now()) }]); + setNewEvent({ id: '', title: '', category: '', location: '', time: '', description: '' }); + }; + + const handleEditEvent = (updatedEvent: Event) => { + setEvents(events.map(event => (event.id === updatedEvent.id ? updatedEvent : event))); + setSelectedEvent(null); + }; + + const handleDeleteEvent = (eventId: string) => { + setEvents(events.filter(event => event.id !== eventId)); + setSelectedEvent(null); + }; + + return ( +
+
+ setNewEvent({ ...newEvent, title: e.target.value })} + /> + setNewEvent({ ...newEvent, category: e.target.value })} + /> + setNewEvent({ ...newEvent, location: e.target.value })} + /> + setNewEvent({ ...newEvent, time: e.target.value })} + /> +