diff --git a/eslint.config.js b/eslint.config.js
index 6c4dd8b1c..7f946c537 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -92,7 +92,7 @@ export default [
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'react-refresh/only-export-components': [
- 'warn',
+ 'off',
{ allowConstantExport: true },
],
diff --git a/package-lock.json b/package-lock.json
index 08368695c..1c54f0f2f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "rs-site",
"version": "0.0.0",
"dependencies": {
+ "class-variance-authority": "^0.7.0",
"classnames": "^2.5.1",
"dayjs": "^1.11.11",
"nextjs-toploader": "^1.6.12",
@@ -3632,6 +3633,17 @@
"node": ">=8"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz",
+ "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==",
+ "dependencies": {
+ "clsx": "2.0.0"
+ },
+ "funding": {
+ "url": "https://joebell.co.uk"
+ }
+ },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
@@ -3749,6 +3761,14 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/clsx": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
+ "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
diff --git a/package.json b/package.json
index ec3e361dc..e323e3148 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"prepare": "husky"
},
"dependencies": {
+ "class-variance-authority": "^0.7.0",
"classnames": "^2.5.1",
"dayjs": "^1.11.11",
"nextjs-toploader": "^1.6.12",
diff --git a/src/app/layouts/base-layout/components/partnered/partnered.tsx b/src/app/layouts/base-layout/components/partnered/partnered.tsx
index 8d7cb0c7d..df306a941 100644
--- a/src/app/layouts/base-layout/components/partnered/partnered.tsx
+++ b/src/app/layouts/base-layout/components/partnered/partnered.tsx
@@ -1,11 +1,12 @@
import { AwsLogo, EpamLogo, GithubLogo, JetBrainsLogo } from '@/shared/icons';
+import { WidgetTitle } from '@/shared/ui/widget-title';
import './partnered.scss';
export const Partnered = () => (
-
Partnered with
+
Partnered with
diff --git a/src/app/styles/index.scss b/src/app/styles/index.scss
index f10730213..7b688c0aa 100644
--- a/src/app/styles/index.scss
+++ b/src/app/styles/index.scss
@@ -33,6 +33,10 @@ li {
padding: 0;
}
+h2 {
+ margin: 0;
+}
+
figure {
margin: 0;
padding: 0;
diff --git a/src/app/styles/normalize.scss b/src/app/styles/normalize.scss
index 25ef0e31c..677bed312 100644
--- a/src/app/styles/normalize.scss
+++ b/src/app/styles/normalize.scss
@@ -35,10 +35,11 @@ main {
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
+ * delete h1 margin
*/
h1 {
- margin: 0.67em 0;
+ margin: 0;
font-size: 2em;
}
diff --git a/src/entities/courses/ui/general/general.tsx b/src/entities/courses/ui/general/general.tsx
index 3cc35ed42..9eff99c1a 100644
--- a/src/entities/courses/ui/general/general.tsx
+++ b/src/entities/courses/ui/general/general.tsx
@@ -1,5 +1,5 @@
import { Link } from 'react-router-dom';
-import { Title } from '@/shared/ui/title';
+import { WidgetTitle } from '@/shared/ui/widget-title';
import './general.scss';
@@ -7,7 +7,7 @@ export const General = () => {
return (
-
+
General
Materials
diff --git a/src/entities/courses/ui/main/main.tsx b/src/entities/courses/ui/main/main.tsx
index 31d69ddeb..a9eca7f41 100644
--- a/src/entities/courses/ui/main/main.tsx
+++ b/src/entities/courses/ui/main/main.tsx
@@ -1,6 +1,6 @@
import mentors from '@/shared/assets/mentor-with-his-students.webp';
import Image from '@/shared/ui/image';
-import { Title, TitleType } from '@/shared/ui/title';
+import { MainTitle } from '@/shared/ui/main-title';
import './main.scss';
@@ -10,7 +10,7 @@ export const Main = () => {
-
+ Our Courses
Journey to full stack mastery
diff --git a/src/shared/ui/main-title/index.ts b/src/shared/ui/main-title/index.ts
new file mode 100644
index 000000000..effb75a0e
--- /dev/null
+++ b/src/shared/ui/main-title/index.ts
@@ -0,0 +1 @@
+export { MainTitle } from './main-title';
diff --git a/src/shared/ui/main-title/main-title.module.scss b/src/shared/ui/main-title/main-title.module.scss
new file mode 100644
index 000000000..ea996ec80
--- /dev/null
+++ b/src/shared/ui/main-title/main-title.module.scss
@@ -0,0 +1,11 @@
+.title {
+ font-size: 96px;
+ font-weight: 700;
+ color: $color-black;
+ text-align: left;
+ letter-spacing: -0.04em;
+
+ @include media-tablet {
+ font-size: 60px;
+ }
+}
diff --git a/src/shared/ui/main-title/main-title.test.tsx b/src/shared/ui/main-title/main-title.test.tsx
new file mode 100644
index 000000000..5cd1cba82
--- /dev/null
+++ b/src/shared/ui/main-title/main-title.test.tsx
@@ -0,0 +1,38 @@
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { MainTitle, cx } from './main-title';
+
+describe('MainTitle component', () => {
+ it('renders without crashing', () => {
+ render(
TestTitle);
+ const title = screen.getByText('TestTitle');
+
+ expect(title).toBeInTheDocument();
+ });
+
+ it('displays h1 tag', () => {
+ render(
);
+ const element = screen.getByRole('heading', { level: 1 });
+
+ expect(element).toBeInTheDocument();
+ });
+
+ it('displays styles correctly', () => {
+ render(
);
+ const element = screen.getByRole('heading', { level: 1 });
+
+ expect(element).toHaveClass(cx('title'));
+ });
+
+ it('renders children correctly', () => {
+ render(
+
+ Child element
+ ,
+ );
+
+ const child = screen.getByText('Child element');
+
+ expect(child).toBeInTheDocument();
+ });
+});
diff --git a/src/shared/ui/main-title/main-title.tsx b/src/shared/ui/main-title/main-title.tsx
new file mode 100644
index 000000000..7f1c7878c
--- /dev/null
+++ b/src/shared/ui/main-title/main-title.tsx
@@ -0,0 +1,22 @@
+import { HTMLAttributes } from 'react';
+import { VariantProps, cva } from 'class-variance-authority';
+import classNames from 'classnames/bind';
+
+import styles from './main-title.module.scss';
+
+type MainTitleProps = HTMLAttributes
& VariantProps;
+
+export const cx = classNames.bind(styles);
+
+const mainTitleVariants = cva(cx('title'));
+
+export const MainTitle = ({ children, className, ...props }: MainTitleProps) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/shared/ui/title/index.ts b/src/shared/ui/title/index.ts
deleted file mode 100644
index db61d4bcd..000000000
--- a/src/shared/ui/title/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { Title } from './title';
-export { TitleType } from './types';
diff --git a/src/shared/ui/title/title.scss b/src/shared/ui/title/title.scss
deleted file mode 100644
index fbd48d76c..000000000
--- a/src/shared/ui/title/title.scss
+++ /dev/null
@@ -1,58 +0,0 @@
-.title {
- @include media-tablet {
- font-size: 36px;
- line-height: 44px;
- }
-
- display: flex;
- flex-direction: row;
- align-items: flex-start;
-
- font-size: 44px;
- font-weight: 500;
- line-height: 52px;
- color: $color-black;
- text-align: left;
- letter-spacing: -0.04em;
-
- &.big {
- @include media-tablet {
- font-size: 36px;
- line-height: 44px;
- }
-
- font-size: 52px;
- line-height: 64px;
- }
-
- &.bigger {
- @include media-laptop {
- font-size: 96px;
- line-height: 1.2em;
- }
-
- font-size: 75px;
- font-weight: 600;
- line-height: 77px;
- letter-spacing: -0.04em;
- }
-
- &.extra-big {
- @include media-laptop {
- font-size: 77px;
- }
-
- @include media-tablet {
- font-size: 61px;
- }
-
- font-size: 96px;
- font-weight: 700;
- line-height: 1.2em;
- }
-
- & .before {
- margin-right: 8px;
- color: $color-red;
- }
-}
diff --git a/src/shared/ui/title/title.test.tsx b/src/shared/ui/title/title.test.tsx
deleted file mode 100644
index 841231266..000000000
--- a/src/shared/ui/title/title.test.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import { describe, expect, it } from 'vitest';
-import { Title } from './title';
-import { TitleType } from './types';
-
-describe('Title component', () => {
- it('renders without crashing', () => {
- render();
- const title = screen.getByText('TestTitle');
-
- expect(title).toBeInTheDocument();
- });
-
- it('applies correct CSS based on Title Type Extra Big', () => {
- render();
- const title = screen.getByText('TestTitle');
-
- expect(title.parentElement).toHaveClass('extra-big');
- });
-
- it('displays an asterisk when hasAsterisk prop is set', () => {
- render();
- const asterisk = screen.getByText('*');
-
- expect(asterisk).toBeInTheDocument();
- });
-
- it('displays lines when hasLines prop is set', () => {
- render();
- const lines = screen.getByText('‖');
-
- expect(lines).toBeInTheDocument();
- });
-
- it('renders children correctly', () => {
- render(
-
- Child element
- ,
- );
-
- const child = screen.getByText('Child element');
-
- expect(child).toBeInTheDocument();
- });
-});
diff --git a/src/shared/ui/title/title.tsx b/src/shared/ui/title/title.tsx
deleted file mode 100644
index 4c8a5d694..000000000
--- a/src/shared/ui/title/title.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { PropsWithChildren } from 'react';
-import { TitleType } from './types';
-
-import './title.scss';
-
-type TitleProps = PropsWithChildren<{
- text?: string;
- type?: TitleType;
- hasAsterisk?: boolean;
- hasLines?: boolean;
-}>;
-
-export const Title = ({ text, type, hasAsterisk, hasLines, children }: TitleProps) => {
- const titleType = type ?? TitleType.Regular;
-
- return (
-
- {hasLines &&
‖}
- {hasAsterisk &&
*}
-
- {text}
- {children}
-
-
- );
-};
diff --git a/src/shared/ui/title/types.ts b/src/shared/ui/title/types.ts
deleted file mode 100644
index 87874ac05..000000000
--- a/src/shared/ui/title/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum TitleType {
- Regular = 'regular',
- Big = 'big',
- Bigger = 'bigger',
- ExtraBig = 'extra-big',
-}
diff --git a/src/shared/ui/widget-title/index.ts b/src/shared/ui/widget-title/index.ts
new file mode 100644
index 000000000..11764e165
--- /dev/null
+++ b/src/shared/ui/widget-title/index.ts
@@ -0,0 +1 @@
+export { WidgetTitle } from './widget-title';
diff --git a/src/shared/ui/widget-title/widget-title.module.scss b/src/shared/ui/widget-title/widget-title.module.scss
new file mode 100644
index 000000000..b640adcd4
--- /dev/null
+++ b/src/shared/ui/widget-title/widget-title.module.scss
@@ -0,0 +1,43 @@
+.title {
+ font-weight: $font-weight-medium;
+ letter-spacing: -0.04em;
+
+ &::before {
+ margin-right: 8px;
+ color: $color-red;
+ }
+}
+
+.small {
+ font-size: 36px;
+ line-height: 44px;
+ letter-spacing: 0;
+}
+
+.medium {
+ font-size: 44px;
+ line-height: 52px;
+
+ @include media-tablet {
+ font-size: 36px;
+ line-height: 44px;
+ }
+}
+
+.large {
+ font-size: 52px;
+ line-height: 64px;
+
+ @include media-tablet {
+ font-size: 36px;
+ line-height: 44px;
+ }
+}
+
+.lines::before {
+ content: '‖';
+}
+
+.asterisk::before {
+ content: '*';
+}
diff --git a/src/shared/ui/widget-title/widget-title.test.tsx b/src/shared/ui/widget-title/widget-title.test.tsx
new file mode 100644
index 000000000..43f4a2289
--- /dev/null
+++ b/src/shared/ui/widget-title/widget-title.test.tsx
@@ -0,0 +1,66 @@
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { WidgetTitle, cx } from './widget-title';
+
+describe('WidgetTitle component', () => {
+ it('renders without crashing', () => {
+ render(TestTitle);
+ const title = screen.getByText('TestTitle');
+
+ expect(title).toBeInTheDocument();
+ });
+
+ it('displays h2 tag', () => {
+ render();
+ const element = screen.getByRole('heading', { level: 2 });
+
+ expect(element).toBeInTheDocument();
+ });
+
+ it('displays size correctly when size=small', () => {
+ render(TestTitle);
+ const element = screen.getByRole('heading', { level: 2 });
+
+ expect(element).toHaveClass(cx('small'));
+ });
+
+ it('displays size correctly when size=medium', () => {
+ render(TestTitle);
+ const element = screen.getByRole('heading', { level: 2 });
+
+ expect(element).toHaveClass(cx('medium'));
+ });
+
+ it('displays size correctly when size=large', () => {
+ render(TestTitle);
+ const element = screen.getByRole('heading', { level: 2 });
+
+ expect(element).toHaveClass(cx('large'));
+ });
+
+ it('displays an asterisk when mods=asterisk', () => {
+ render(TestTitle);
+ const asterisk = screen.getByRole('heading', { level: 2 });
+
+ expect(asterisk).toHaveClass(cx('asterisk'));
+ });
+
+ it('displays lines when mods=lines', () => {
+ render(TestTitle);
+ const lines = screen.getByRole('heading', { level: 2 });
+
+ expect(lines).toHaveClass(cx('lines'));
+ });
+
+ it('renders children correctly', () => {
+ render(
+
+ Child element
+ ,
+ );
+
+ const child = screen.getByText('Child element');
+
+ expect(child).toBeInTheDocument();
+ });
+});
diff --git a/src/shared/ui/widget-title/widget-title.tsx b/src/shared/ui/widget-title/widget-title.tsx
new file mode 100644
index 000000000..fdb777f40
--- /dev/null
+++ b/src/shared/ui/widget-title/widget-title.tsx
@@ -0,0 +1,42 @@
+import { HTMLAttributes } from 'react';
+import { type VariantProps, cva } from 'class-variance-authority';
+import classNames from 'classnames/bind';
+
+import styles from './widget-title.module.scss';
+
+type WidgetTitleProps = HTMLAttributes & VariantProps;
+
+export const cx = classNames.bind(styles);
+
+const widgetTitleVariants = cva(cx('title'), {
+ variants: {
+ mods: {
+ lines: cx('lines'),
+ asterisk: cx('asterisk'),
+ },
+ size: {
+ small: cx('small'),
+ medium: cx('medium'),
+ large: cx('large'),
+ },
+ },
+ defaultVariants: {
+ size: 'medium',
+ mods: null,
+ },
+});
+
+export const WidgetTitle = ({ children, size, mods, className, ...props }: WidgetTitleProps) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/widgets/about-home/ui/about.tsx b/src/widgets/about-home/ui/about.tsx
index 37bca8a25..b72029308 100644
--- a/src/widgets/about-home/ui/about.tsx
+++ b/src/widgets/about-home/ui/about.tsx
@@ -2,7 +2,7 @@ import image from '@/shared/assets/about.webp';
import Image from '@/shared/ui/image';
import { Paragraph } from '@/shared/ui/paragraph';
import { Subtitle } from '@/shared/ui/subtitle';
-import { Title } from '@/shared/ui/title';
+import { WidgetTitle } from '@/shared/ui/widget-title';
import './about.scss';
@@ -11,7 +11,7 @@ export const About = () => {
-
+
Who we are
{
-
+
About RS School
RS School offers a unique learning experience as a free, community-based online
diff --git a/src/widgets/about-video/ui/about-video.tsx b/src/widgets/about-video/ui/about-video.tsx
index e11ff2199..30f18c7f6 100644
--- a/src/widgets/about-video/ui/about-video.tsx
+++ b/src/widgets/about-video/ui/about-video.tsx
@@ -1,4 +1,4 @@
-import { Title } from '@/shared/ui/title';
+import { WidgetTitle } from '@/shared/ui/widget-title';
import './about-video.scss';
@@ -13,7 +13,7 @@ export const AboutVideo = ({ lang = 'en' }: AboutVideoProps) => {
return (
-
+
{localizedContent[lang].title}