Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 68 additions & 10 deletions src/components/Title/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,79 @@
# Title

`type: "title"`

`title:`
The **Title** component is used to display a section heading, optionally with a subtitle (description) and configurable styles, alignment, and link behavior.

---

## 🔧 Props

### `title`

Defines the main title content.
It can be either a simple string or an object with the following structure:

| Property | Type | Default | Description |
| ------------- | ------------------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `text` | `string` | — | Main title text. |
| `textSize` | `'s'` \| `'m'` \| `'l'` \|`'xs'` \| `'sm'` | `'m'` | Title font size. |
| `url` | `string` | — | Optional URL. When set, the title becomes a clickable link, and an arrow is automatically displayed at the end. |
| `resetMargin` | `boolean` | `true` | When `true`, removes automatic top margin. When `false`, the top margin depends on the `textSize` (see [_Margins_](#margins) below). |
| `anchor` | `string` | — | Optional anchor ID for navigation. |
| `justify` | `'start'` \| `'center'` \| `'end'` | `'start'` | Text alignment. |
| `urlTitle` | `string` | — | Accessibility title attribute for the link. |
| `onClick` | `() => void` | — | Optional click handler. |
| `custom` | `string \| React.ReactNode` | — | Custom React node or text appended to the title. |
| `navTitle` | `string` | — | Optional navigation title for use in table of contents. |

---

### `subtitle`

`subtitle?: string`

Optional subtitle (description) text.
Supports **YFM (Yandex Flavored Markdown)** formatting.

---

### `className`

`className?: string`

Optional CSS class name for the container.

---

### `colSizes`

- `text: string` - Title text
`colSizes?: GridColumnSizesType`

- `textSize?: 's' | 'm' | 'l'` — Title font size
Grid column size configuration for responsive layouts.
Default: `{ all: 12, sm: 8 }`.

- `url?: string` — URL for a redirect on clicking the title, an arrow is automatically added at the end.
---

- `resetMargin?: boolean` - default `true`. Without this property `margin-top` will be proportional to `textSize` (see section _Margins_ below)
### `id`

`description: string` - text (with YFM support)
`id?: string`

**Margins for title without reset:**
Optional HTML `id` attribute for the title container.

`textSize s - top: m`
---

`textSize m - top: l`
## 🧩 Example

`textSize l - top: xl`
```tsx
<Title
title={{
text: 'Section Heading',
textSize: 'l',
url: 'https://example.com',
urlTitle: 'Go to example',
resetMargin: false,
}}
subtitle="**This is** a subtitle with [YFM](https://example.com) support."
colSizes={{all: 12, sm: 6}}
/>
```
10 changes: 2 additions & 8 deletions src/components/Title/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,12 @@ import './Title.scss';

const b = block('title');

export interface TitleProps extends TitleParams {
export interface TitleProps extends TitleParams, ClassNameProps {
colSizes?: GridColumnSizesType;
id?: string;
}

const Title = ({
title,
subtitle,
className,
colSizes = {all: 12, sm: 8},
id,
}: TitleProps & ClassNameProps) => {
const Title = ({title, subtitle, className, colSizes = {all: 12, sm: 8}, id}: TitleProps) => {
if (!title && !subtitle) {
return null;
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 26 additions & 20 deletions src/components/Title/__stories__/Title.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import {Meta, StoryFn} from '@storybook/react';
import {Meta, StoryFn, StoryObj} from '@storybook/react';

import {yfmTransform} from '../../../../.storybook/utils';
import {ClassNameProps, TitleItemProps} from '../../../models';
import {blockTransform, yfmTransform} from '../../../../.storybook/utils';
import {CustomBlock, TitleItemProps} from '../../../models';
import Title, {TitleProps} from '../Title';

import data from './data.json';

export default {
component: Title,
title: 'Components/Title',
} as Meta;
component: Title,
parameters: {
layout: 'centered',
controls: {expanded: true},
},
} as Meta<TitleProps>;

const DefaultTemplate: StoryFn<TitleProps & ClassNameProps> = (args) => <Title {...args} />;
const DefaultTemplate: StoryFn<TitleProps> = (args) => (
<Title {...(blockTransform(args as unknown as CustomBlock) as TitleProps)} />
);

const SizesTemplate: StoryFn<TitleProps & ClassNameProps> = (args) => {
const SizesTemplate: StoryFn<TitleProps> = (args) => {
const titleItemObjectProps = typeof args.title === 'object' ? args.title : {};

return (
<div>
{Object.entries(data.sizes).map(([size, props]) => (
Expand All @@ -40,16 +45,15 @@ const DefaultArgs = {
subtitle: yfmTransform(data.default.content.subtitle),
};

export const Default = DefaultTemplate.bind({});
export const TitleLink = DefaultTemplate.bind({});
export const CustomTitle = DefaultTemplate.bind({});
export const Sizes = SizesTemplate.bind({});
export const SizesWithLinks = SizesTemplate.bind({});
export const TitleWithoutDescription = SizesTemplate.bind({});
export const Default: StoryObj<typeof Title> = DefaultTemplate.bind({});
export const TitleLink: StoryObj<typeof Title> = DefaultTemplate.bind({});
export const CustomTitle: StoryObj<typeof Title> = DefaultTemplate.bind({});
export const Sizes: StoryObj<typeof Title> = SizesTemplate.bind({});
export const SizesWithLinks: StoryObj<typeof Title> = SizesTemplate.bind({});
export const TitleWithoutDescription: StoryObj<typeof Title> = SizesTemplate.bind({});
export const WithCustomColSizes: StoryObj<typeof Title> = DefaultTemplate.bind({});

Default.args = {
...DefaultArgs,
} as TitleProps;
Default.args = {...DefaultArgs} as TitleProps;
TitleLink.args = {
...DefaultArgs,
title: data.titleLink.content.title,
Expand All @@ -58,13 +62,15 @@ CustomTitle.args = {
...DefaultArgs,
title: data.customTitle.content.title,
} as TitleProps;
Sizes.args = {
...DefaultArgs,
} as TitleProps;
Sizes.args = {...DefaultArgs} as TitleProps;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image Controls should be array

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this story and in "Sizes With Link" and in "Title Without Description"

SizesWithLinks.args = {
...DefaultArgs,
title: data.titleLink.content.title,
} as TitleProps;
TitleWithoutDescription.args = {
title: data.default.content.title,
} as TitleProps;
WithCustomColSizes.args = {
...DefaultArgs,
colSizes: {all: 6, sm: 6, md: 4},
};
2 changes: 1 addition & 1 deletion src/components/Title/__stories__/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"title": {
"text": "Lorem ipsum",
"url": "https://example.com",
"custom": "Some react node",
"custom": "⭐️",
"urlTitle": "Example website. Opens in a new window"
}
}
Expand Down
59 changes: 59 additions & 0 deletions src/components/Title/__tests__/Title.visual.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {test} from '../../../../playwright/core/index';

import {
CustomTitle,
Default,
Sizes,
SizesWithLinks,
TitleLink,
TitleWithoutDescription,
WithCustomColSizes,
} from './helpers';

test.describe('Title', () => {
test('render stories <Default>', async ({mount, expectScreenshot, defaultDelay}) => {
await mount(<Default />);
await defaultDelay();
await expectScreenshot({skipTheme: 'dark'});
});

test('render stories <TitleLink>', async ({mount, expectScreenshot, defaultDelay}) => {
await mount(<TitleLink />);
await defaultDelay();
await expectScreenshot({skipTheme: 'dark'});
});

test('render stories <CustomTitle>', async ({mount, expectScreenshot, defaultDelay}) => {
await mount(<CustomTitle />);
await defaultDelay();
await expectScreenshot({skipTheme: 'dark'});
});

test('render stories <Sizes>', async ({mount, expectScreenshot, defaultDelay}) => {
await mount(<Sizes />);
await defaultDelay();
await expectScreenshot({skipTheme: 'dark'});
});

test('render stories <SizesWithLinks>', async ({mount, expectScreenshot, defaultDelay}) => {
await mount(<SizesWithLinks />);
await defaultDelay();
await expectScreenshot({skipTheme: 'dark'});
});

test('render stories <TitleWithoutDescription>', async ({
mount,
expectScreenshot,
defaultDelay,
}) => {
await mount(<TitleWithoutDescription />);
await defaultDelay();
await expectScreenshot({skipTheme: 'dark'});
});

test('render stories <WithCustomColSizes>', async ({mount, expectScreenshot, defaultDelay}) => {
await mount(<WithCustomColSizes />);
await defaultDelay();
await expectScreenshot({skipTheme: 'dark'});
});
});
15 changes: 15 additions & 0 deletions src/components/Title/__tests__/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {composeStories} from '@storybook/react';

import * as TitleStories from '../__stories__/Title.stories';

const composed = composeStories(TitleStories);

export const {
CustomTitle,
Default,
Sizes,
SizesWithLinks,
TitleLink,
TitleWithoutDescription,
WithCustomColSizes,
} = composed;