From 9904d54d282be7598af5fbdc32d30cdab25cf258 Mon Sep 17 00:00:00 2001 From: rotemergty5 <153999862+rotemergty5@users.noreply.github.com> Date: Sun, 17 May 2026 02:30:56 +0300 Subject: [PATCH 1/7] Create skeleton card component --- .../UI/Skeleton/SGLContentCardSkeleton.css | 169 ++++++++++++++++++ .../UI/Skeleton/SGLContentCardSkeleton.tsx | 167 +++++++++++++++++ src/components/UI/Skeleton/SGLSkeleton.tsx | 47 +++++ 3 files changed, 383 insertions(+) create mode 100644 src/components/UI/Skeleton/SGLContentCardSkeleton.css create mode 100644 src/components/UI/Skeleton/SGLContentCardSkeleton.tsx create mode 100644 src/components/UI/Skeleton/SGLSkeleton.tsx diff --git a/src/components/UI/Skeleton/SGLContentCardSkeleton.css b/src/components/UI/Skeleton/SGLContentCardSkeleton.css new file mode 100644 index 0000000..edd42da --- /dev/null +++ b/src/components/UI/Skeleton/SGLContentCardSkeleton.css @@ -0,0 +1,169 @@ +.sgl-card-container { + display: flex; + flex-direction: column; + gap: 16px; + + width: 100%; + + overflow-y: auto; + overflow-x: hidden; + + padding-right: 4px; + + box-sizing: border-box; + margin: 10px; +} + +.sgl-header { + display: flex; + align-items: center; + gap: 12px; + + width: 100%; +} + +.sgl-avatar-wrapper { + position: relative; + + width: 40px; + height: 40px; + +} + +.sgl-avatar-image { + position: absolute; + + top: 0; + left: 0; + + width: 40px; + height: 40px; + + border-radius: 50%; + object-fit: cover; + opacity: 1; +} + +.sgl-text-content { + flex: 1; +} + +.sgl-text-wrapper { + position: relative; + width: 'fit-content'; +} + +.sgl-subtitle-wrapper { + margin-top: -11px; +} + +.sgl-overlay-text { + position: absolute; + + top: 50%; + left: 12px; + + transform: translateY(-50%); + + color: white; + + pointer-events: none; + + white-space: nowrap; +} + +.sgl-title { + font-size: 14px; + font-weight: 600; +} + +.sgl-subtitle { + font-size: 12px; +} + +.sgl-image-section { + display: flex; + flex-direction: column; + gap: 8px; + + width: 100%; +} + +.sgl-image-wrapper { + position: relative; + + width: 100%; + height: 180px; + + overflow: hidden; + + border-radius: 16px; +} + +.sgl-image { + position: absolute; + + inset: 0; + + width: 100%; + height: 100%; + + object-fit: cover; + scale: 1.1; +} + +.image-description-wrapper { + position: relative; + width: 100%; + min-height: 32px; + + padding: 8px 12px; + box-sizing: border-box; + + border-radius: 8px; + overflow: hidden; +} + +.image-description-background { + position: absolute; + inset: 0; + + opacity: 0.5; + border-radius: 8px; +} + +.image-description { + position: relative; + + color: #fff; + font-size: 13px; + line-height: 1.4; + + word-break: break-word; + overflow-wrap: anywhere; +} + +.sgl-button-wrapper { + position: relative; + + width: fit-content; + + max-width: 100%; +} + +.sgl-button-text { + position: absolute; + + top: 50%; + left: 50%; + + transform: translate(-50%, -50%); + + color: white; + + font-size: 14px; + + white-space: nowrap; + + pointer-events: none; +} \ No newline at end of file diff --git a/src/components/UI/Skeleton/SGLContentCardSkeleton.tsx b/src/components/UI/Skeleton/SGLContentCardSkeleton.tsx new file mode 100644 index 0000000..0eda8d6 --- /dev/null +++ b/src/components/UI/Skeleton/SGLContentCardSkeleton.tsx @@ -0,0 +1,167 @@ +import Box from '@mui/material/Box' +import Typography from '@mui/material/Typography' +import './SGLContentCardSkeleton.css' +import { SGLSkeleton } from './SGLSkeleton' + +interface SGLContentCardSkeletonProps { + color?: string + avatarSrc?: string + + + title?: string + subtitle?: string + buttonText?: string + + imageSrc?: string + imageDescription?: string + + imageHeight?: number + + showAvatar?: boolean + showButton?: boolean + showImage?: boolean +} + +export const SGLContentCardSkeleton = ({ + color = '#8F3DFF', + avatarSrc = 'https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png', + + title = 'Loading title', + subtitle = 'Loading subtitle', + buttonText = 'Loading button', + + imageSrc = 'https://images.unsplash.com/photo-1506744038136-46273834b3fb', + imageDescription = 'Image description', + + imageHeight = 180, + + showAvatar = true, + showButton = true, + showImage = true, +}: SGLContentCardSkeletonProps) => { + return ( + + {/* Header */} + + {showAvatar && ( + + + + {avatarSrc && ( + + )} + + )} + + + {/* Title */} + + + + + {title} + + + + {/* Subtitle */} + + + + + {subtitle} + + + + + + {/* Image */} + {showImage && ( + + + + + {imageSrc && ( + + )} + + + {/* Image Description */} + + + + + {imageDescription} + + + + )} + + + {/* Button */} + {showButton && ( + + + + + {buttonText} + + + )} + + ) +} \ No newline at end of file diff --git a/src/components/UI/Skeleton/SGLSkeleton.tsx b/src/components/UI/Skeleton/SGLSkeleton.tsx new file mode 100644 index 0000000..46d898b --- /dev/null +++ b/src/components/UI/Skeleton/SGLSkeleton.tsx @@ -0,0 +1,47 @@ +import Skeleton, { type SkeletonProps as MuiSkeletonProps } from '@mui/material/Skeleton' +import { type ReactNode } from 'react' +import { type SxProps, type Theme } from '@mui/material/styles' + +interface SGLSkeletonProps extends Omit { + children?: ReactNode + width?: number | string + height?: number | string + textSize?: number | string + skeletonColor?: string + radius?: number | string + opacity?: number + sx?: SxProps +} + +export const SGLSkeleton = ({ + children, + variant = 'text', + animation = 'wave', + width, + height, + textSize = '1rem', + skeletonColor = '#8F3DFF', + radius, + opacity = 1, + sx, + ...props +}: SGLSkeletonProps) => { + return ( + + {children} + + ) +} \ No newline at end of file From fd424d636ba99dd4dfab30ddb67ea885ea44c0d3 Mon Sep 17 00:00:00 2001 From: Rotem Date: Tue, 19 May 2026 18:42:51 +0300 Subject: [PATCH 2/7] Update skeleton component --- .../UI/Skeleton/SGLContentCardSkeleton.css | 169 ------------------ .../UI/Skeleton/SGLContentCardSkeleton.tsx | 167 ----------------- src/components/UI/Skeleton/SGLSkeleton.tsx | 38 ++-- 3 files changed, 10 insertions(+), 364 deletions(-) delete mode 100644 src/components/UI/Skeleton/SGLContentCardSkeleton.css delete mode 100644 src/components/UI/Skeleton/SGLContentCardSkeleton.tsx diff --git a/src/components/UI/Skeleton/SGLContentCardSkeleton.css b/src/components/UI/Skeleton/SGLContentCardSkeleton.css deleted file mode 100644 index edd42da..0000000 --- a/src/components/UI/Skeleton/SGLContentCardSkeleton.css +++ /dev/null @@ -1,169 +0,0 @@ -.sgl-card-container { - display: flex; - flex-direction: column; - gap: 16px; - - width: 100%; - - overflow-y: auto; - overflow-x: hidden; - - padding-right: 4px; - - box-sizing: border-box; - margin: 10px; -} - -.sgl-header { - display: flex; - align-items: center; - gap: 12px; - - width: 100%; -} - -.sgl-avatar-wrapper { - position: relative; - - width: 40px; - height: 40px; - -} - -.sgl-avatar-image { - position: absolute; - - top: 0; - left: 0; - - width: 40px; - height: 40px; - - border-radius: 50%; - object-fit: cover; - opacity: 1; -} - -.sgl-text-content { - flex: 1; -} - -.sgl-text-wrapper { - position: relative; - width: 'fit-content'; -} - -.sgl-subtitle-wrapper { - margin-top: -11px; -} - -.sgl-overlay-text { - position: absolute; - - top: 50%; - left: 12px; - - transform: translateY(-50%); - - color: white; - - pointer-events: none; - - white-space: nowrap; -} - -.sgl-title { - font-size: 14px; - font-weight: 600; -} - -.sgl-subtitle { - font-size: 12px; -} - -.sgl-image-section { - display: flex; - flex-direction: column; - gap: 8px; - - width: 100%; -} - -.sgl-image-wrapper { - position: relative; - - width: 100%; - height: 180px; - - overflow: hidden; - - border-radius: 16px; -} - -.sgl-image { - position: absolute; - - inset: 0; - - width: 100%; - height: 100%; - - object-fit: cover; - scale: 1.1; -} - -.image-description-wrapper { - position: relative; - width: 100%; - min-height: 32px; - - padding: 8px 12px; - box-sizing: border-box; - - border-radius: 8px; - overflow: hidden; -} - -.image-description-background { - position: absolute; - inset: 0; - - opacity: 0.5; - border-radius: 8px; -} - -.image-description { - position: relative; - - color: #fff; - font-size: 13px; - line-height: 1.4; - - word-break: break-word; - overflow-wrap: anywhere; -} - -.sgl-button-wrapper { - position: relative; - - width: fit-content; - - max-width: 100%; -} - -.sgl-button-text { - position: absolute; - - top: 50%; - left: 50%; - - transform: translate(-50%, -50%); - - color: white; - - font-size: 14px; - - white-space: nowrap; - - pointer-events: none; -} \ No newline at end of file diff --git a/src/components/UI/Skeleton/SGLContentCardSkeleton.tsx b/src/components/UI/Skeleton/SGLContentCardSkeleton.tsx deleted file mode 100644 index 0eda8d6..0000000 --- a/src/components/UI/Skeleton/SGLContentCardSkeleton.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import Box from '@mui/material/Box' -import Typography from '@mui/material/Typography' -import './SGLContentCardSkeleton.css' -import { SGLSkeleton } from './SGLSkeleton' - -interface SGLContentCardSkeletonProps { - color?: string - avatarSrc?: string - - - title?: string - subtitle?: string - buttonText?: string - - imageSrc?: string - imageDescription?: string - - imageHeight?: number - - showAvatar?: boolean - showButton?: boolean - showImage?: boolean -} - -export const SGLContentCardSkeleton = ({ - color = '#8F3DFF', - avatarSrc = 'https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png', - - title = 'Loading title', - subtitle = 'Loading subtitle', - buttonText = 'Loading button', - - imageSrc = 'https://images.unsplash.com/photo-1506744038136-46273834b3fb', - imageDescription = 'Image description', - - imageHeight = 180, - - showAvatar = true, - showButton = true, - showImage = true, -}: SGLContentCardSkeletonProps) => { - return ( - - {/* Header */} - - {showAvatar && ( - - - - {avatarSrc && ( - - )} - - )} - - - {/* Title */} - - - - - {title} - - - - {/* Subtitle */} - - - - - {subtitle} - - - - - - {/* Image */} - {showImage && ( - - - - - {imageSrc && ( - - )} - - - {/* Image Description */} - - - - - {imageDescription} - - - - )} - - - {/* Button */} - {showButton && ( - - - - - {buttonText} - - - )} - - ) -} \ No newline at end of file diff --git a/src/components/UI/Skeleton/SGLSkeleton.tsx b/src/components/UI/Skeleton/SGLSkeleton.tsx index 46d898b..5b009f7 100644 --- a/src/components/UI/Skeleton/SGLSkeleton.tsx +++ b/src/components/UI/Skeleton/SGLSkeleton.tsx @@ -1,47 +1,29 @@ -import Skeleton, { type SkeletonProps as MuiSkeletonProps } from '@mui/material/Skeleton' -import { type ReactNode } from 'react' -import { type SxProps, type Theme } from '@mui/material/styles' +import Skeleton, { + type SkeletonProps as MuiSkeletonProps, +} from '@mui/material/Skeleton' -interface SGLSkeletonProps extends Omit { - children?: ReactNode - width?: number | string - height?: number | string - textSize?: number | string +import type { SxProps, Theme } from '@mui/material/styles' + +interface SGLSkeletonProps + extends Omit { skeletonColor?: string - radius?: number | string - opacity?: number sx?: SxProps } export const SGLSkeleton = ({ - children, - variant = 'text', - animation = 'wave', - width, - height, - textSize = '1rem', skeletonColor = '#8F3DFF', - radius, - opacity = 1, + animation = 'wave', sx, ...props }: SGLSkeletonProps) => { return ( - {children} - + /> ) } \ No newline at end of file From fdd253b80e33163cdd8cf5494f91435520dd3cdc Mon Sep 17 00:00:00 2001 From: Rotem Date: Tue, 26 May 2026 20:29:41 +0300 Subject: [PATCH 3/7] update skeleton --- src/components/UI/Skeleton/SGLSkeleton.tsx | 20 +++++++++----------- src/components/UI/Skeleton/styles.ts | 12 ++++++++++++ 2 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 src/components/UI/Skeleton/styles.ts diff --git a/src/components/UI/Skeleton/SGLSkeleton.tsx b/src/components/UI/Skeleton/SGLSkeleton.tsx index 5b009f7..a0e49b9 100644 --- a/src/components/UI/Skeleton/SGLSkeleton.tsx +++ b/src/components/UI/Skeleton/SGLSkeleton.tsx @@ -2,26 +2,24 @@ import Skeleton, { type SkeletonProps as MuiSkeletonProps, } from '@mui/material/Skeleton' -import type { SxProps, Theme } from '@mui/material/styles' +import type { CSSProperties } from '@mui/material/styles' +import { skeletonStyles } from './styles' -interface SGLSkeletonProps - extends Omit { - skeletonColor?: string - sx?: SxProps +interface SGLSkeletonProps extends Omit { + style?: CSSProperties } export const SGLSkeleton = ({ - skeletonColor = '#8F3DFF', animation = 'wave', - sx, + style, ...props }: SGLSkeletonProps) => { return ( ({ + ...skeletonStyles(theme), + ...style, + })} animation={animation} {...props} /> diff --git a/src/components/UI/Skeleton/styles.ts b/src/components/UI/Skeleton/styles.ts new file mode 100644 index 0000000..3a68c35 --- /dev/null +++ b/src/components/UI/Skeleton/styles.ts @@ -0,0 +1,12 @@ +import type { Theme } from '@mui/material/styles' + +export const skeletonStyles = (theme: Theme) => { + return { + backgroundColor: theme.palette.lightGrey.main, + opacity: 1, + + '&.MuiSkeleton-wave::after': { + animationDuration: '1.6s', + }, + } +} \ No newline at end of file From fb96d6f16b3cfaff96c8178645354d22243adcaa Mon Sep 17 00:00:00 2001 From: Rotem Date: Mon, 1 Jun 2026 14:05:03 +0300 Subject: [PATCH 4/7] fix prettier issues in skeleton component --- src/components/UI/Skeleton/SGLSkeleton.tsx | 12 +++--------- src/components/UI/Skeleton/styles.ts | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/components/UI/Skeleton/SGLSkeleton.tsx b/src/components/UI/Skeleton/SGLSkeleton.tsx index a0e49b9..d16f85c 100644 --- a/src/components/UI/Skeleton/SGLSkeleton.tsx +++ b/src/components/UI/Skeleton/SGLSkeleton.tsx @@ -1,6 +1,4 @@ -import Skeleton, { - type SkeletonProps as MuiSkeletonProps, -} from '@mui/material/Skeleton' +import Skeleton, { type SkeletonProps as MuiSkeletonProps } from '@mui/material/Skeleton' import type { CSSProperties } from '@mui/material/styles' import { skeletonStyles } from './styles' @@ -9,11 +7,7 @@ interface SGLSkeletonProps extends Omit { style?: CSSProperties } -export const SGLSkeleton = ({ - animation = 'wave', - style, - ...props -}: SGLSkeletonProps) => { +export const SGLSkeleton = ({ animation = 'wave', style, ...props }: SGLSkeletonProps) => { return ( ({ @@ -24,4 +18,4 @@ export const SGLSkeleton = ({ {...props} /> ) -} \ No newline at end of file +} diff --git a/src/components/UI/Skeleton/styles.ts b/src/components/UI/Skeleton/styles.ts index 3a68c35..af089dd 100644 --- a/src/components/UI/Skeleton/styles.ts +++ b/src/components/UI/Skeleton/styles.ts @@ -9,4 +9,4 @@ export const skeletonStyles = (theme: Theme) => { animationDuration: '1.6s', }, } -} \ No newline at end of file +} From c5b6a600b6f71f668b9f8b528279b7dde6c31c0d Mon Sep 17 00:00:00 2001 From: Rotem Date: Mon, 1 Jun 2026 15:33:20 +0300 Subject: [PATCH 5/7] Add unit tests for SGLSelect --- src/components/UI/Select/SGLselect.test.tsx | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/components/UI/Select/SGLselect.test.tsx diff --git a/src/components/UI/Select/SGLselect.test.tsx b/src/components/UI/Select/SGLselect.test.tsx new file mode 100644 index 0000000..8d46c20 --- /dev/null +++ b/src/components/UI/Select/SGLselect.test.tsx @@ -0,0 +1,71 @@ +import { describe, it, expect, afterEach } from 'vitest' +import '@testing-library/jest-dom/vitest' +import { render, screen, fireEvent, cleanup } from '@testing-library/react' +import { ThemeProvider } from '@mui/material/styles' +import { theme } from '../../../theme' + +import { SGLSelect } from './SGLSelect' + +afterEach(() => { + cleanup() +}) + +describe('SGLSelect', () => { + const options = ['Apple', 'Banana', 'Orange'] + + it('should select the relevant item', () => { + render( + + + , + ) + + const select = screen.getByRole('combobox') + + fireEvent.mouseDown(select) + + const option = screen.getByText('Banana') + fireEvent.click(option) + + expect(select).toHaveTextContent('Banana') + }) + + it('should not select any item when dropdown is opened and closed', () => { + render( + + + , + ) + + const select = screen.getByRole('combobox') + + fireEvent.mouseDown(select) + fireEvent.keyDown(select, { key: 'Escape' }) + + expect(select).not.toHaveTextContent('Banana') + }) + + it('should change the selected value when selecting a different option', () => { + render( + + + , + ) + + const select = screen.getByRole('combobox') + + fireEvent.mouseDown(select) + + const bananaOption = screen.getByText('Banana') + fireEvent.click(bananaOption) + + expect(select).toHaveTextContent('Banana') + + fireEvent.mouseDown(select) + + const orangeOption = screen.getByText('Orange') + fireEvent.click(orangeOption) + + expect(select).toHaveTextContent('Orange') + }) +}) From 6a127349660255cb9f0b96222c4ac596caaab14e Mon Sep 17 00:00:00 2001 From: Rotem Date: Sat, 6 Jun 2026 16:15:38 +0300 Subject: [PATCH 6/7] Revert "Fix SGLSelect tests" This reverts commit 50f9bf2f37bf7bfd83c1420482a784f399bc576b. --- src/components/UI/Select/SGLselect.test.tsx | 43 +++++++-------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/components/UI/Select/SGLselect.test.tsx b/src/components/UI/Select/SGLselect.test.tsx index 8d46c20..5b18970 100644 --- a/src/components/UI/Select/SGLselect.test.tsx +++ b/src/components/UI/Select/SGLselect.test.tsx @@ -1,8 +1,6 @@ import { describe, it, expect, afterEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { render, screen, fireEvent, cleanup } from '@testing-library/react' -import { ThemeProvider } from '@mui/material/styles' -import { theme } from '../../../theme' import { SGLSelect } from './SGLSelect' @@ -14,11 +12,7 @@ describe('SGLSelect', () => { const options = ['Apple', 'Banana', 'Orange'] it('should select the relevant item', () => { - render( - - - , - ) + render() const select = screen.getByRole('combobox') @@ -28,14 +22,11 @@ describe('SGLSelect', () => { fireEvent.click(option) expect(select).toHaveTextContent('Banana') - }) + }) + it('should not select any item when dropdown is opened and closed', () => { - render( - - - , - ) + render() const select = screen.getByRole('combobox') @@ -46,26 +37,22 @@ describe('SGLSelect', () => { }) it('should change the selected value when selecting a different option', () => { - render( - - - , - ) + render() - const select = screen.getByRole('combobox') + const select = screen.getByRole('combobox') - fireEvent.mouseDown(select) + fireEvent.mouseDown(select) - const bananaOption = screen.getByText('Banana') - fireEvent.click(bananaOption) + const bananaOption = screen.getByText('Banana') + fireEvent.click(bananaOption) - expect(select).toHaveTextContent('Banana') + expect(select).toHaveTextContent('Banana') - fireEvent.mouseDown(select) + fireEvent.mouseDown(select) - const orangeOption = screen.getByText('Orange') - fireEvent.click(orangeOption) + const orangeOption = screen.getByText('Orange') + fireEvent.click(orangeOption) - expect(select).toHaveTextContent('Orange') - }) + expect(select).toHaveTextContent('Orange') }) +}) \ No newline at end of file From 538638981c960f9dc8d613bbef65940e37d95a8d Mon Sep 17 00:00:00 2001 From: Rotem Date: Sat, 6 Jun 2026 16:35:21 +0300 Subject: [PATCH 7/7] Revert "Add unit tests for SGLSelect" This reverts commit c5b6a600b6f71f668b9f8b528279b7dde6c31c0d. --- src/components/UI/Select/SGLselect.test.tsx | 58 --------------------- 1 file changed, 58 deletions(-) delete mode 100644 src/components/UI/Select/SGLselect.test.tsx diff --git a/src/components/UI/Select/SGLselect.test.tsx b/src/components/UI/Select/SGLselect.test.tsx deleted file mode 100644 index 5b18970..0000000 --- a/src/components/UI/Select/SGLselect.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { describe, it, expect, afterEach } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { render, screen, fireEvent, cleanup } from '@testing-library/react' - -import { SGLSelect } from './SGLSelect' - -afterEach(() => { - cleanup() -}) - -describe('SGLSelect', () => { - const options = ['Apple', 'Banana', 'Orange'] - - it('should select the relevant item', () => { - render() - - const select = screen.getByRole('combobox') - - fireEvent.mouseDown(select) - - const option = screen.getByText('Banana') - fireEvent.click(option) - - expect(select).toHaveTextContent('Banana') - }) - - - it('should not select any item when dropdown is opened and closed', () => { - render() - - const select = screen.getByRole('combobox') - - fireEvent.mouseDown(select) - fireEvent.keyDown(select, { key: 'Escape' }) - - expect(select).not.toHaveTextContent('Banana') - }) - - it('should change the selected value when selecting a different option', () => { - render() - - const select = screen.getByRole('combobox') - - fireEvent.mouseDown(select) - - const bananaOption = screen.getByText('Banana') - fireEvent.click(bananaOption) - - expect(select).toHaveTextContent('Banana') - - fireEvent.mouseDown(select) - - const orangeOption = screen.getByText('Orange') - fireEvent.click(orangeOption) - - expect(select).toHaveTextContent('Orange') -}) -}) \ No newline at end of file