chore: format codebase and move SectionAccordion to entities/Section

This commit is contained in:
Ilia Mashkov
2026-04-23 20:52:43 +03:00
parent 8aff27f8ac
commit 1d333fd945
73 changed files with 1201 additions and 1153 deletions
+2 -2
View File
@@ -1,2 +1,2 @@
export { Section, Container } from './ui/Section'
export type { SectionBackground, ContainerSize } from './ui/Section'
export { Section, Container } from './ui/Section';
export type { SectionBackground, ContainerSize } from './ui/Section';
+23 -11
View File
@@ -1,14 +1,14 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { Section, Container } from './Section'
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { Section, Container } from './Section';
const meta: Meta<typeof Section> = {
title: 'Shared/Section',
component: Section,
}
};
export default meta
export default meta;
type Story = StoryObj<typeof Section>
type Story = StoryObj<typeof Section>;
export const AllBackgrounds: Story = {
render: () => (
@@ -16,32 +16,44 @@ export const AllBackgrounds: Story = {
<Section background="ochre" className="py-12">
<Container>
<h2>Ochre Section</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua.
</p>
</Container>
</Section>
<Section background="slate" className="py-12">
<Container>
<h2>Slate Section</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua.
</p>
</Container>
</Section>
<Section background="white" className="py-12">
<Container>
<h2>White Section</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua.
</p>
</Container>
</Section>
</div>
),
}
};
export const Bordered: Story = {
render: () => (
<Section background="ochre" bordered className="py-12">
<Container>
<h2>Bordered Section</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
</p>
</Container>
</Section>
),
}
};
+70 -62
View File
@@ -1,95 +1,103 @@
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Section, Container } from './Section'
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Section, Container } from './Section';
describe('Section', () => {
describe('rendering', () => {
it('renders a section element', () => {
const { container } = render(<Section>content</Section>)
expect(container.querySelector('section')).toBeInTheDocument()
})
const { container } = render(<Section>content</Section>);
expect(container.querySelector('section')).toBeInTheDocument();
});
it('renders children', () => {
render(<Section><span>hello</span></Section>)
expect(screen.getByText('hello')).toBeInTheDocument()
})
})
render(
<Section>
<span>hello</span>
</Section>,
);
expect(screen.getByText('hello')).toBeInTheDocument();
});
});
describe('background variants', () => {
it('defaults to ochre background', () => {
const { container } = render(<Section>x</Section>)
expect(container.querySelector('section')).toHaveClass('bg-ochre-clay', 'text-carbon-black')
})
const { container } = render(<Section>x</Section>);
expect(container.querySelector('section')).toHaveClass('bg-ochre-clay', 'text-carbon-black');
});
it('applies slate background', () => {
const { container } = render(<Section background="slate">x</Section>)
expect(container.querySelector('section')).toHaveClass('bg-slate-indigo', 'text-ochre-clay')
})
const { container } = render(<Section background="slate">x</Section>);
expect(container.querySelector('section')).toHaveClass('bg-slate-indigo', 'text-ochre-clay');
});
it('applies white background', () => {
const { container } = render(<Section background="white">x</Section>)
expect(container.querySelector('section')).toHaveClass('bg-white', 'text-carbon-black')
})
})
const { container } = render(<Section background="white">x</Section>);
expect(container.querySelector('section')).toHaveClass('bg-white', 'text-carbon-black');
});
});
describe('bordered', () => {
it('no border classes by default', () => {
const { container } = render(<Section>x</Section>)
const el = container.querySelector('section')!
expect(el).not.toHaveClass('brutal-border-top')
expect(el).not.toHaveClass('brutal-border-bottom')
})
const { container } = render(<Section>x</Section>);
const el = container.querySelector('section')!;
expect(el).not.toHaveClass('brutal-border-top');
expect(el).not.toHaveClass('brutal-border-bottom');
});
it('adds top and bottom borders when bordered=true', () => {
const { container } = render(<Section bordered>x</Section>)
const el = container.querySelector('section')!
expect(el).toHaveClass('brutal-border-top')
expect(el).toHaveClass('brutal-border-bottom')
})
})
const { container } = render(<Section bordered>x</Section>);
const el = container.querySelector('section')!;
expect(el).toHaveClass('brutal-border-top');
expect(el).toHaveClass('brutal-border-bottom');
});
});
describe('className', () => {
it('applies custom className', () => {
const { container } = render(<Section className="py-16">x</Section>)
expect(container.querySelector('section')).toHaveClass('py-16')
})
})
})
const { container } = render(<Section className="py-16">x</Section>);
expect(container.querySelector('section')).toHaveClass('py-16');
});
});
});
describe('Container', () => {
describe('rendering', () => {
it('renders a div with children', () => {
render(<Container><span>inner</span></Container>)
expect(screen.getByText('inner')).toBeInTheDocument()
})
})
render(
<Container>
<span>inner</span>
</Container>,
);
expect(screen.getByText('inner')).toBeInTheDocument();
});
});
describe('size variants', () => {
it('defaults to max-w-7xl', () => {
const { container } = render(<Container>x</Container>)
expect(container.firstChild).toHaveClass('max-w-7xl')
})
const { container } = render(<Container>x</Container>);
expect(container.firstChild).toHaveClass('max-w-7xl');
});
it('wide applies max-w-[1920px]', () => {
const { container } = render(<Container size="wide">x</Container>)
expect(container.firstChild).toHaveClass('max-w-[1920px]')
})
const { container } = render(<Container size="wide">x</Container>);
expect(container.firstChild).toHaveClass('max-w-[1920px]');
});
it('ultra-wide applies max-w-[2560px]', () => {
const { container } = render(<Container size="ultra-wide">x</Container>)
expect(container.firstChild).toHaveClass('max-w-[2560px]')
})
})
const { container } = render(<Container size="ultra-wide">x</Container>);
expect(container.firstChild).toHaveClass('max-w-[2560px]');
});
});
describe('layout', () => {
it('centers content horizontally', () => {
const { container } = render(<Container>x</Container>)
expect(container.firstChild).toHaveClass('mx-auto')
})
const { container } = render(<Container>x</Container>);
expect(container.firstChild).toHaveClass('mx-auto');
});
it('applies horizontal padding', () => {
const { container } = render(<Container>x</Container>)
expect(container.firstChild).toHaveClass('px-6')
})
})
const { container } = render(<Container>x</Container>);
expect(container.firstChild).toHaveClass('px-6');
});
});
describe('className', () => {
it('applies custom className', () => {
const { container } = render(<Container className="my-custom">x</Container>)
expect(container.firstChild).toHaveClass('my-custom')
})
})
})
const { container } = render(<Container className="my-custom">x</Container>);
expect(container.firstChild).toHaveClass('my-custom');
});
});
});
+18 -28
View File
@@ -1,82 +1,72 @@
import type { ReactNode } from 'react'
import { cn } from '$shared/lib'
import type { ReactNode } from 'react';
import { cn } from '$shared/lib';
export type SectionBackground = 'ochre' | 'slate' | 'white'
export type ContainerSize = 'default' | 'wide' | 'ultra-wide'
export type SectionBackground = 'ochre' | 'slate' | 'white';
export type ContainerSize = 'default' | 'wide' | 'ultra-wide';
interface SectionProps {
/**
* Section content
*/
children: ReactNode
children: ReactNode;
/**
* Background color variant
* @default 'ochre'
*/
background?: SectionBackground
background?: SectionBackground;
/**
* Adds top and bottom brutal borders
* @default false
*/
bordered?: boolean
bordered?: boolean;
/**
* CSS classes
*/
className?: string
className?: string;
}
const BACKGROUNDS: Record<SectionBackground, string> = {
ochre: 'bg-ochre-clay text-carbon-black',
slate: 'bg-slate-indigo text-ochre-clay',
white: 'bg-white text-carbon-black',
}
};
/**
* Full-width page section with background and optional borders.
*/
export function Section({ children, background = 'ochre', bordered = false, className }: SectionProps) {
return (
<section
className={cn(
BACKGROUNDS[background],
bordered && 'brutal-border-top brutal-border-bottom',
className,
)}
>
<section className={cn(BACKGROUNDS[background], bordered && 'brutal-border-top brutal-border-bottom', className)}>
{children}
</section>
)
);
}
interface ContainerProps {
/**
* Container content
*/
children: ReactNode
children: ReactNode;
/**
* Max-width constraint
* @default 'default'
*/
size?: ContainerSize
size?: ContainerSize;
/**
* CSS classes
*/
className?: string
className?: string;
}
const SIZES: Record<ContainerSize, string> = {
'default': 'max-w-7xl',
'wide': 'max-w-[1920px]',
default: 'max-w-7xl',
wide: 'max-w-[1920px]',
'ultra-wide': 'max-w-[2560px]',
}
};
/**
* Centered content container with responsive horizontal padding.
*/
export function Container({ children, size = 'default', className }: ContainerProps) {
return (
<div className={cn(SIZES[size], 'mx-auto px-6 md:px-12 lg:px-16', className)}>
{children}
</div>
)
return <div className={cn(SIZES[size], 'mx-auto px-6 md:px-12 lg:px-16', className)}>{children}</div>;
}