Skip to content

Commit

Permalink
feat(Stories): add unstable versions of Stories and StoriesGroup comp…
Browse files Browse the repository at this point in the history
…onents (#245)
  • Loading branch information
DarkGenius authored Dec 10, 2024
1 parent 5f3a336 commit 8ac2cdf
Show file tree
Hide file tree
Showing 34 changed files with 1,885 additions and 0 deletions.
22 changes: 22 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,31 @@
"version": "3.12.5",
"description": "",
"license": "MIT",
"exports": {
".": {
"types": "./build/esm/index.d.ts",
"require": "./build/cjs/index.js",
"import": "./build/esm/index.js"
},
"./unstable": {
"types": "./build/esm/components/unstable/index.d.ts",
"require": "./build/cjs/components/unstable/index.js",
"import": "./build/esm/components/unstable/index.js"
}
},
"main": "./build/cjs/index.js",
"module": "./build/esm/index.js",
"types": "./build/esm/index.d.ts",
"typesVersions": {
"*": {
"index.d.ts": [
"./build/esm/index.d.ts"
],
"unstable": [
"./build/esm/components/unstable/index.d.ts"
]
}
},
"sideEffects": [
"*.css",
"*.scss"
Expand Down
65 changes: 65 additions & 0 deletions src/components/unstable/Stories/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## Stories

Component for displaying stories. It looks like a carousel in a modal with given places to display text and media.

### PropTypes

| Property | Type | Required | Default | Description |
| :------------------ | :-------------- | :------- | :------ | :----------------------------------------------- |
| open | `Boolean` || | Visibility flag |
| items | `StoriesItem[]` || | List of stories to display |
| initialStoryIndex | `Number` | | 0 | Index of the first story to be displayed |
| onClose | `Function` | | | Action on close |
| onPreviousClick | `Function` | | | Action when switching to previous story |
| onNextClick | `Function` | | | Action when switching to next story |
| disableOutsideClick | `Boolean` | | true | If `true`, do not close stories on click outside |
| className | `string` | | | Stories modal class |

### StoriesItem object

| Field | Type | Required | Default | Description |
| --------------- | ------------------------ | -------- | ------- | ---------------------------------------- |
| title | `String` | | | Title |
| description | `String` | | | Main text |
| url | `String` | | | Link to display more information |
| media | `StoriesItemMedia` | | | Media content |
| firstAction | `ButtonProps` | | | Custom action button props |
| secondAction | `ButtonProps` | | | Custom action button props |
| textBlockStyle | `StoriesTextBlockStyle` || | Props for styling text content in Story |
| mediaBlockStyle | `StoriesMediaBlockStyle` || | Props for styling media content in Story |
| textColorStyles | `StoriesItemTextStyles` | | | Props for styling text color in Story |

### StoriesItemMedia object

| Field | Type | Required | Default | Description |
| --------- | -------- | -------- | ------- | -------------------------------------------------- |
| type | `String` || | Content type (`image` or `video`) |
| url | `String` || | File link |
| url2x | `String` | | | File link for Retina display (only used for image) |
| posterUrl | `String` | | | Poster URL (only used for video) |

### StoriesItemTextStyles object

| Field | Type | Required | Default | Description |
| ---------------- | -------- | -------- | ------- | -------------------------------- |
| titleColor | `String` | | | Apply color to Story title |
| descriptionColor | `String` | | | Apply color to Story description |
| counterColor | `String` | | | Apply color to Story counter |

#### Usage example

```jsx harmony
<Stories
open
items={[
{
title: 'Story title',
description: 'Story text',
type: 'image',
media: {
url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-2.png',
},
},
]}
/>
```
16 changes: 16 additions & 0 deletions src/components/unstable/Stories/Stories.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@use '../variables';

$block: '.#{variables.$ns}stories';

#{$block} {
--g-modal-border-radius: var(--g-spacing-5);
--g-modal-margin: var(--g-spacing-5);

& .g-modal__content-wrapper {
overflow-x: initial;
}

&__modal-content {
border-radius: var(--g-spacing-5);
}
}
128 changes: 128 additions & 0 deletions src/components/unstable/Stories/Stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from 'react';

import type {ModalCloseReason} from '@gravity-ui/uikit';
import {Modal} from '@gravity-ui/uikit';

import {block} from '../utils/cn';

import {IndexType, StoriesLayout} from './components/StoriesLayout/StoriesLayout';
import {useSyncWithLS} from './hooks';
import type {StoriesItem} from './types';

import './Stories.scss';

const b = block('stories');

export interface StoriesProps {
open: boolean;
items: StoriesItem[];
onClose?: (
event: MouseEvent | KeyboardEvent | React.MouseEvent<HTMLElement, MouseEvent>,
reason: ModalCloseReason | 'closeButtonClick',
) => void;
initialStoryIndex?: number;
onPreviousClick?: (storyIndex: number) => void;
onNextClick?: (storyIndex: number) => void;
disableOutsideClick?: boolean;
className?: string;
syncInTabsKey?: string;
}

export function Stories({
open,
onClose,
items,
onPreviousClick,
onNextClick,
initialStoryIndex = 0,
disableOutsideClick = true,
className,
syncInTabsKey,
}: StoriesProps) {
const [storyIndex, setStoryIndex] = React.useState(initialStoryIndex);

const handleClose = React.useCallback<NonNullable<StoriesProps['onClose']>>(
(event, reason) => {
onClose?.(event, reason);
},
[onClose],
);

const {callback: closeWithLS} = useSyncWithLS<NonNullable<StoriesProps['onClose']>>({
callback: (event, reason) => {
onClose?.(event, reason);
},
uniqueKey: `close-story-${syncInTabsKey}`,
});

const handleButtonClose = React.useCallback<
(event: MouseEvent | KeyboardEvent | React.MouseEvent<HTMLElement, MouseEvent>) => void
>(
(event) => {
handleClose(event, 'closeButtonClick');
if (syncInTabsKey) closeWithLS(event, 'closeButtonClick');
},
[handleClose, syncInTabsKey, closeWithLS],
);

const handleGotoPrevious = React.useCallback(() => {
setStoryIndex((currentStoryIndex) => {
if (currentStoryIndex <= 0) {
return 0;
}

const newIndex = currentStoryIndex - 1;
onPreviousClick?.(newIndex);
return newIndex;
});
}, [onPreviousClick]);

const handleGotoNext = React.useCallback(() => {
setStoryIndex((currentStoryIndex) => {
if (currentStoryIndex >= items.length - 1) {
return items.length - 1;
}

const newIndex = currentStoryIndex + 1;
onNextClick?.(newIndex);
return newIndex;
});
}, [items, onNextClick]);

if (items.length === 0) {
return null;
}

// case when items has changed and index has ceased to be valid
if (items[storyIndex] === undefined) {
const correctIndex = items[initialStoryIndex] === undefined ? 0 : initialStoryIndex;
setStoryIndex(correctIndex);

return null;
}

const indexType =
(items.length === 1 && IndexType.Single) ||
(storyIndex === 0 && IndexType.Start) ||
(storyIndex === items.length - 1 && IndexType.End) ||
IndexType.InProccess;

return (
<Modal
open={open}
onClose={handleClose}
disableOutsideClick={disableOutsideClick}
className={b()}
contentClassName={b('modal-content', className)}
>
<StoriesLayout
items={items}
storyIndex={storyIndex}
indexType={indexType}
handleButtonClose={handleButtonClose}
handleGotoNext={handleGotoNext}
handleGotoPrevious={handleGotoPrevious}
/>
</Modal>
);
}
Loading

0 comments on commit 8ac2cdf

Please sign in to comment.