From 6f6816655f6e6f56747c3f244251e832d11aa66c Mon Sep 17 00:00:00 2001 From: aobityutskiy Date: Wed, 4 Sep 2024 14:07:14 +0300 Subject: [PATCH] feat!: refactor Stories component, add props for manage story style --- src/components/Stories/README.md | 38 ++- src/components/Stories/Stories.scss | 11 +- src/components/Stories/Stories.tsx | 4 - .../Stories/__stories__/Stories.stories.tsx | 274 ++++++++++++++++-- .../components/ImageView/ImageView.scss | 27 +- .../components/ImageView/ImageView.tsx | 21 +- .../MediaRenderer/MediaRenderer.tsx | 8 +- .../StoriesLayout/StoriesLayout.scss | 108 +++---- .../StoriesLayout/StoriesLayout.tsx | 216 ++++++++------ .../components/VideoView/VideoView.scss | 37 ++- .../components/VideoView/VideoView.tsx | 42 +-- src/components/Stories/types.ts | 29 +- src/components/StoriesGroup/README.md | 2 + src/components/StoriesGroup/StoriesGroup.scss | 4 + .../__stories__/StoriesGroup.stories.tsx | 86 +++++- 15 files changed, 672 insertions(+), 235 deletions(-) diff --git a/src/components/Stories/README.md b/src/components/Stories/README.md index 8c0c1191..db530550 100644 --- a/src/components/Stories/README.md +++ b/src/components/Stories/README.md @@ -14,24 +14,37 @@ Component for displaying stories. It looks like a carousel in a modal with given | 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 | -| action | `ButtonProps` | | | Custom action button props for the last step | ### 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 | +| 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` | | image | Content type (`image` or `video`) | -| url | `String` | ✓ | | File link | -| posterUrl | `String` | | | Poster URL (only used for video) | +| 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 @@ -42,6 +55,7 @@ Component for displaying stories. It looks like a carousel in a modal with given { title: 'Story title', description: 'Story text', + type: 'image', media: { url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-2.png', }, diff --git a/src/components/Stories/Stories.scss b/src/components/Stories/Stories.scss index d8807848..bb5639a0 100644 --- a/src/components/Stories/Stories.scss +++ b/src/components/Stories/Stories.scss @@ -1,13 +1,16 @@ @use '../variables'; $block: '.#{variables.$ns}stories'; -$borderRadius: 20px; #{$block} { - --g-modal-border-radius: #{$borderRadius}; - --g-modal-margin: 20px; + --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: $borderRadius; + border-radius: var(--g-spacing-5); } } diff --git a/src/components/Stories/Stories.tsx b/src/components/Stories/Stories.tsx index bad2bbd8..19247054 100644 --- a/src/components/Stories/Stories.tsx +++ b/src/components/Stories/Stories.tsx @@ -5,7 +5,6 @@ import {Modal} from '@gravity-ui/uikit'; import {block} from '../utils/cn'; -import type {StoriesLayoutProps} from './components/StoriesLayout/StoriesLayout'; import {IndexType, StoriesLayout} from './components/StoriesLayout/StoriesLayout'; import {useSyncWithLS} from './hooks'; import type {StoriesItem} from './types'; @@ -26,7 +25,6 @@ export interface StoriesProps { onNextClick?: (storyIndex: number) => void; disableOutsideClick?: boolean; className?: string; - action?: StoriesLayoutProps['action']; syncInTabsKey?: string; } @@ -39,7 +37,6 @@ export function Stories({ initialStoryIndex = 0, disableOutsideClick = true, className, - action, syncInTabsKey, }: StoriesProps) { const [storyIndex, setStoryIndex] = React.useState(initialStoryIndex); @@ -125,7 +122,6 @@ export function Stories({ handleButtonClose={handleButtonClose} handleGotoNext={handleGotoNext} handleGotoPrevious={handleGotoPrevious} - action={action} /> ); diff --git a/src/components/Stories/__stories__/Stories.stories.tsx b/src/components/Stories/__stories__/Stories.stories.tsx index bb0ca091..73120e39 100644 --- a/src/components/Stories/__stories__/Stories.stories.tsx +++ b/src/components/Stories/__stories__/Stories.stories.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import {Button} from '@gravity-ui/uikit'; +import {Button, Flex, Text} from '@gravity-ui/uikit'; import type {Meta, StoryFn} from '@storybook/react'; import type {StoriesProps} from '../Stories'; import {Stories} from '../Stories'; import type {StoriesItem} from '../types'; +import {StoriesMediaBlockStyle, StoriesTextBlockStyle} from '../types'; export default { title: 'Components/Stories', @@ -15,18 +16,71 @@ export default { const items: StoriesItem[] = [ { title: 'New navigation', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + firstAction: { + children: 'First button', + view: 'action', + }, + secondAction: { + children: 'Second button', + view: 'normal', + }, description: + 'At the top of the panel is the service navigation for each service. ' + + 'Below are common navigation elements: a component for switching between accounts ' + + 'and organizations, settings, help center, search, notifications, favorites.' + + 'At the top of the panel is the service navigation for each service. ' + + 'Below are common navigation elements: a component for switching between accounts ' + + 'and organizations, settings, help center, search, notifications, favorites.' + + 'At the top of the panel is the service navigation for each service. ' + + 'Below are common navigation elements: a component for switching between accounts ' + + 'and organizations, settings, help center, search, notifications, favorites.' + + 'At the top of the panel is the service navigation for each service. ' + + 'Below are common navigation elements: a component for switching between accounts ' + + 'and organizations, settings, help center, search, notifications, favorites.' + 'At the top of the panel is the service navigation for each service. ' + 'Below are common navigation elements: a component for switching between accounts ' + 'and organizations, settings, help center, search, notifications, favorites.', url: 'https://yandex.eu', media: { - url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-2.png', + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-6.png', + }, + }, + { + title: 'New navigation (2)', + description: 'A little more about the new navigation', + textBlockStyle: StoriesTextBlockStyle.Transparent, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + firstAction: { + children: 'First button', + view: 'action', + }, + secondAction: { + children: 'Second button', + view: 'normal', + }, + media: { + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-6.png', + type: 'image', + }, + }, + { + title: 'New navigation (2)', + description: 'A little more about the new navigation', + textBlockStyle: StoriesTextBlockStyle.Transparent, + mediaBlockStyle: StoriesMediaBlockStyle.HalfSize, + media: { + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/sample_960x400_ocean_with_audio.mp4', + type: 'video', }, }, { title: 'New navigation (2)', description: 'A little more about the new navigation', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, media: { url: 'https://storage.yandexcloud.net/uikit-storybook-assets/sample_960x400_ocean_with_audio.mp4', type: 'video', @@ -35,38 +89,212 @@ const items: StoriesItem[] = [ { title: 'New navigation (3)', description: 'Switch to the new navigation right now', + textBlockStyle: StoriesTextBlockStyle.Transparent, + mediaBlockStyle: StoriesMediaBlockStyle.HalfSizeWithMargins, media: { - url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-4.png', + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-7.png', }, }, ]; -const DefaultTemplate: StoryFn = (props: StoriesProps) => { - const [visible, setVisible] = React.useState(props.open); - - React.useEffect(() => { - setVisible(props.open); - }, [props.open]); +const BaseStory = ({description, items}: {description: string; items: StoriesProps['items']}) => { + const [visible, setVisible] = React.useState(false); return ( - -
- -
+ + {description} + + { setVisible(false); }} /> -
+ + ); +}; + +const DefaultTemplate: StoryFn = () => { + const itemsFirst: StoriesProps['items'] = [ + { + title: 'Default story with simple text', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.HalfSizeWithMargins, + description: + 'This story has default value for props textBlockStyle = "card" and mediaBlockStyle = "half-size-with-margins" ', + media: { + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-7.png', + }, + }, + { + title: 'Story without margins in media block', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.HalfSize, + description: + 'Props value: textBlockStyle = "card", mediaBlockStyle = "half-size-with-margins" and media type = "image"', + media: { + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-7.png', + }, + }, + { + title: 'Story with full-size media block', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + description: + 'Props value: textBlockStyle = "card", mediaBlockStyle = "full-size" and media type = "image"', + media: { + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-6.png', + }, + }, + { + title: 'Story with full-size media block and transparent text-block', + textBlockStyle: StoriesTextBlockStyle.Transparent, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + description: + 'Props value: textBlockStyle = "transparent", mediaBlockStyle = "full-size" and media type = "image"', + media: { + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-6.png', + }, + }, + ]; + + const itemsSecond: StoriesProps['items'] = [ + { + title: 'Story with video half-size', + textBlockStyle: StoriesTextBlockStyle.Transparent, + mediaBlockStyle: StoriesMediaBlockStyle.HalfSize, + description: + 'Props value: textBlockStyle = "transparent", mediaBlockStyle = "half-size" and media type = "video"', + media: { + type: 'video', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/sample_960x400_ocean_with_audio.mp4', + }, + }, + { + title: 'Story with video half-size with margins and extra actions', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.HalfSizeWithMargins, + firstAction: { + children: 'First button', + view: 'action', + }, + secondAction: { + children: 'Second button', + view: 'normal', + }, + description: + 'Props value: textBlockStyle = "card", mediaBlockStyle = "half-size-with-margins" and media type = "video"', + media: { + type: 'video', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/sample_960x400_ocean_with_audio.mp4', + }, + }, + { + title: 'Story with video full-size', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + description: + 'Props value: textBlockStyle = "card", mediaBlockStyle = "full-size" and media type = "video"', + media: { + type: 'video', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/sample_960x400_ocean_with_audio.mp4', + }, + }, + ]; + + const itemsThird: StoriesProps['items'] = [ + { + title: 'Story with full-size image, extra actions, long text and default text colors', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + firstAction: { + children: 'First button', + view: 'action', + }, + secondAction: { + children: 'Second button', + view: 'normal', + }, + description: + 'Lorem ipsum odor amet, consectetuer adipiscing elit. Nunc at parturient tristique senectus class duis eget per taciti. Eu rutrum est euismod risus aliquet in. Vehicula habitant nostra enim quis blandit consequat. Blandit ex ut purus; vestibulum accumsan duis? Porttitor accumsan at molestie integer nulla habitant? Egestas urna suscipit eleifend tortor mauris montes vulputate primis?Tempor viverra vitae tempus consectetur egestas? Quam dolor dictumst pellentesque porta; pulvinar conubia placerat risus. Leo at elementum vivamus fermentum erat taciti. Turpis ipsum faucibus primis purus, montes curae eu vel. Lacus metus sagittis dictumst diam libero imperdiet rhoncus neque. Natoque nullam inceptos porttitor integer porttitor nascetur a interdum. Imperdiet scelerisque rutrum congue massa eleifend torquent nisi. Sociosqu libero volutpat nisl orci viverra. Tristique egestas auctor conubia; etiam lectus scelerisque ligula. Magnis ultrices venenatis vivamus hac taciti inceptos leo. Interdum magnis sollicitudin elementum placerat montes. Lacinia platea netus nascetur ornare sociosqu. Inceptos taciti iaculis interdum nisl sodales in eros fermentum. Justo maecenas elementum condimentum feugiat consectetur semper sollicitudin. Primis sodales posuere facilisis donec ipsum efficitur. Faucibus accumsan lectus bibendum rhoncus maecenas, eget aliquam netus. Lectus torquent ut sodales fringilla natoque.', + media: { + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-6.png', + }, + }, + { + title: 'Story with full-size image, transparent text-block and long content with extra actions and custom text colors', + textBlockStyle: StoriesTextBlockStyle.Transparent, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + textColorStyles: { + titleColor: 'var(--g-color-text-brand)', + counterColor: 'var(--g-color-text-brand)', + descriptionColor: 'var(--g-color-text-inverted-primary)', + }, + firstAction: { + children: 'First button', + view: 'action', + }, + secondAction: { + children: 'Second button', + view: 'normal-contrast', + }, + description: + 'Lorem ipsum odor amet, consectetuer adipiscing elit. Nunc at parturient tristique senectus class duis eget per taciti. Eu rutrum est euismod risus aliquet in. Vehicula habitant nostra enim quis blandit consequat. Blandit ex ut purus; vestibulum accumsan duis? Porttitor accumsan at molestie integer nulla habitant? Egestas urna suscipit eleifend tortor mauris montes vulputate primis?Tempor viverra vitae tempus consectetur egestas? Quam dolor dictumst pellentesque porta; pulvinar conubia placerat risus. Leo at elementum vivamus fermentum erat taciti. Turpis ipsum faucibus primis purus, montes curae eu vel. Lacus metus sagittis dictumst diam libero imperdiet rhoncus neque. Natoque nullam inceptos porttitor integer porttitor nascetur a interdum. Imperdiet scelerisque rutrum congue massa eleifend torquent nisi. Sociosqu libero volutpat nisl orci viverra. Tristique egestas auctor conubia; etiam lectus scelerisque ligula. Magnis ultrices venenatis vivamus hac taciti inceptos leo. Interdum magnis sollicitudin elementum placerat montes. Lacinia platea netus nascetur ornare sociosqu. Inceptos taciti iaculis interdum nisl sodales in eros fermentum. Justo maecenas elementum condimentum feugiat consectetur semper sollicitudin. Primis sodales posuere facilisis donec ipsum efficitur. Faucibus accumsan lectus bibendum rhoncus maecenas, eget aliquam netus. Lectus torquent ut sodales fringilla natoque.', + media: { + type: 'image', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/story-picture-7.png', + }, + }, + { + title: 'Story with full-size video, extra actions, long text and default text colors', + textBlockStyle: StoriesTextBlockStyle.Card, + mediaBlockStyle: StoriesMediaBlockStyle.FullSize, + firstAction: { + children: 'First button', + view: 'action', + }, + secondAction: { + children: 'Second button', + view: 'normal', + }, + description: + 'Lorem ipsum odor amet, consectetuer adipiscing elit. Nunc at parturient tristique senectus class duis eget per taciti. Eu rutrum est euismod risus aliquet in. Vehicula habitant nostra enim quis blandit consequat. Blandit ex ut purus; vestibulum accumsan duis? Porttitor accumsan at molestie integer nulla habitant? Egestas urna suscipit eleifend tortor mauris montes vulputate primis?Tempor viverra vitae tempus consectetur egestas? Quam dolor dictumst pellentesque porta; pulvinar conubia placerat risus. Leo at elementum vivamus fermentum erat taciti. Turpis ipsum faucibus primis purus, montes curae eu vel. Lacus metus sagittis dictumst diam libero imperdiet rhoncus neque. Natoque nullam inceptos porttitor integer porttitor nascetur a interdum. Imperdiet scelerisque rutrum congue massa eleifend torquent nisi. Sociosqu libero volutpat nisl orci viverra. Tristique egestas auctor conubia; etiam lectus scelerisque ligula. Magnis ultrices venenatis vivamus hac taciti inceptos leo. Interdum magnis sollicitudin elementum placerat montes. Lacinia platea netus nascetur ornare sociosqu. Inceptos taciti iaculis interdum nisl sodales in eros fermentum. Justo maecenas elementum condimentum feugiat consectetur semper sollicitudin. Primis sodales posuere facilisis donec ipsum efficitur. Faucibus accumsan lectus bibendum rhoncus maecenas, eget aliquam netus. Lectus torquent ut sodales fringilla natoque.', + media: { + type: 'video', + url: 'https://storage.yandexcloud.net/uikit-storybook-assets/sample_960x400_ocean_with_audio.mp4', + }, + }, + ]; + + const exampleDescription = [ + 'Stories with different text and media block styles', + 'Stories with video content', + 'Stories with all features', + ]; + + return ( + + {[itemsFirst, itemsSecond, itemsThird].map((items, index) => { + return ( + + ); + })} + ); }; export const Default = DefaultTemplate.bind({}); @@ -89,10 +317,6 @@ export const WithCustomAction = DefaultTemplate.bind({}); WithCustomAction.args = { open: false, items: [items[0]], - action: { - view: 'action', - children: 'View examples', - }, }; export const WithSyncInTabs = DefaultTemplate.bind({}); diff --git a/src/components/Stories/components/ImageView/ImageView.scss b/src/components/Stories/components/ImageView/ImageView.scss index 417586a4..60c88bcf 100644 --- a/src/components/Stories/components/ImageView/ImageView.scss +++ b/src/components/Stories/components/ImageView/ImageView.scss @@ -2,9 +2,32 @@ $block: '.#{variables.$ns}stories-image-view'; +$borderRadius: 20px; + #{$block} { - width: auto; + width: 100%; + height: 100%; max-width: 100%; max-height: 100%; - margin: auto; + + &_style_half-size { + border-radius: $borderRadius; + } + + &_style_half-size-with-margins { + width: calc(100% - 2 * var(--g-spacing-2)); + height: calc(100% - 2 * var(--g-spacing-2)); + border-radius: $borderRadius; + + margin: var(--g-spacing-2); + } + + &_style_full-size { + position: absolute; + border-radius: $borderRadius; + + inset-block-start: 0; + inset-inline-start: 0; + z-index: 0; + } } diff --git a/src/components/Stories/components/ImageView/ImageView.tsx b/src/components/Stories/components/ImageView/ImageView.tsx index dd2e9814..38b2c746 100644 --- a/src/components/Stories/components/ImageView/ImageView.tsx +++ b/src/components/Stories/components/ImageView/ImageView.tsx @@ -1,18 +1,25 @@ import React from 'react'; import {block} from '../../../utils/cn'; -import type {StoriesItemMedia} from '../../types'; +import type {MediaRendererProps} from '../MediaRenderer/MediaRenderer'; import './ImageView.scss'; const b = block('stories-image-view'); -export interface ImageViewProps { - media: StoriesItemMedia; -} +export interface ImageViewProps extends MediaRendererProps {} -export function ImageView({media}: ImageViewProps) { - const type = media.type || 'image'; +export function ImageView({media, style}: ImageViewProps) { + if (media.type === 'image') { + return ( +
+ ); + } - return type === 'image' ? : null; + return null; } diff --git a/src/components/Stories/components/MediaRenderer/MediaRenderer.tsx b/src/components/Stories/components/MediaRenderer/MediaRenderer.tsx index 81fad6fc..9ab0e746 100644 --- a/src/components/Stories/components/MediaRenderer/MediaRenderer.tsx +++ b/src/components/Stories/components/MediaRenderer/MediaRenderer.tsx @@ -2,15 +2,17 @@ import React from 'react'; import {ImageView, VideoView} from '../../components'; import type {StoriesItemMedia} from '../../types'; +import {StoriesMediaBlockStyle} from '../../types'; export interface MediaRendererProps { media: StoriesItemMedia; + style?: StoriesMediaBlockStyle; } -export function MediaRenderer({media}: MediaRendererProps) { +export function MediaRenderer({media, style}: MediaRendererProps) { return (media.type || 'image') === 'image' ? ( - + ) : ( - + ); } diff --git a/src/components/Stories/components/StoriesLayout/StoriesLayout.scss b/src/components/Stories/components/StoriesLayout/StoriesLayout.scss index 759d0f7f..75048a9b 100644 --- a/src/components/Stories/components/StoriesLayout/StoriesLayout.scss +++ b/src/components/Stories/components/StoriesLayout/StoriesLayout.scss @@ -2,46 +2,33 @@ @use '../../../variables'; $block: '.#{variables.$ns}stories-layout'; -$borderRadius: 20px; + $maxWidth: 1280px; $maxHeight: 640px; $minWidth: 800px; $minHeight: 480px; -$leftPaneBorderRadius: 17px; -$leftPanePadding: 32px; -$rightPanePadding: 68px; -$smallMargin: 8px; -$textBlockMargin: 16px; +$fixedBtnSize: 44px; + +$actionBtnZIndex: 50; +$textContentZIndex: 30; #{$block} { &__wrap-outer { height: calc(100vh - 2 * var(--g-modal-margin)); width: calc(100vw - 2 * var(--g-modal-margin)); - display: flex; - border-radius: $borderRadius; - max-width: $maxWidth; - max-height: $maxHeight; - min-width: $minWidth; - min-height: $minHeight; - background-color: var(--g-color-base-selection); - } - - &__wrap-inner { background-color: var(--g-color-base-background); - border-radius: $borderRadius; + display: flex; + border-radius: var(--g-spacing-5); max-width: $maxWidth; max-height: $maxHeight; min-width: $minWidth; min-height: $minHeight; - width: 100%; - height: 100%; } &__container { display: flex; - background-color: var(--g-color-base-selection); box-shadow: 0 8px 20px var(--g-color-sfx-shadow); - border-radius: $borderRadius; + border-radius: var(--g-spacing-5); position: relative; width: 100%; @@ -49,28 +36,38 @@ $textBlockMargin: 16px; } &__left-pane { - width: 464px; - flex-shrink: 0; - margin-inline-start: $smallMargin; - margin-block: $smallMargin; - background-color: var(--g-color-base-background); - border-radius: $leftPaneBorderRadius; - padding: $leftPanePadding; + flex-basis: calc(50% - var(--g-spacing-4)); + margin-inline: var(--g-spacing-2); + margin-block: var(--g-spacing-2); + padding: var(--g-spacing-8); + display: flex; flex-direction: column; align-items: stretch; + flex-shrink: 0; box-sizing: border-box; + z-index: $textContentZIndex; + + &_blockStyle_card { + background-color: var(--g-color-base-background); + border-radius: var(--g-spacing-3); + } + + &_blockStyle_transparent { + background-color: transparent; + } } &__right-pane { - padding: $rightPanePadding; + flex-basis: 50%; + + width: 100%; + height: 100%; + display: flex; - flex-grow: 1; - align-items: center; } &__counter { - @include mixins.text-body-2(); color: var(--g-color-text-secondary); } @@ -78,51 +75,54 @@ $textBlockMargin: 16px; display: flex; flex-grow: 1; align-items: flex-start; - justify-content: center; + justify-content: flex-start; flex-direction: column; - margin-block-end: $smallMargin; - overflow: hidden; + margin-block-start: var(--g-spacing-10); + overflow-y: scroll; + } + + &__action-block { + margin-block-start: var(--g-spacing-8); } &__text-header { - @include mixins.text-display-2(); color: var(--g-color-text-primary); } &__text-content { - @include mixins.text-body-2(); color: var(--g-color-text-complementary); - overflow-y: scroll; #{$block}__text-header + & { - margin-block-start: $textBlockMargin; + margin-block-start: var(--g-spacing-4); } } &__story-link-block { - margin-block-start: $textBlockMargin; + margin-block-start: var(--g-spacing-4); } - &__controls-block { - display: flex; - gap: #{$smallMargin}; + &__navigation-btn { + --g-border-radius-xl: 50%; - button { - max-width: 50%; + position: absolute; + inset-block-start: calc(50% - ($fixedBtnSize / 2)); + z-index: $actionBtnZIndex; + + &_back { + inset-inline-start: calc(0px - ($fixedBtnSize / 2)); } - } - &__media-block { - position: relative; - display: flex; - width: 100%; - height: 100%; + &_next { + inset-inline-end: calc(0px - ($fixedBtnSize / 2)); + } } &__close-btn { + --g-border-radius-xl: 50%; + position: absolute; - inset-block-start: 14px; - inset-inline-end: 20px; - z-index: 1; + inset-block-start: var(--g-spacing-4); + inset-inline-end: var(--g-spacing-4); + z-index: $actionBtnZIndex; } } diff --git a/src/components/Stories/components/StoriesLayout/StoriesLayout.tsx b/src/components/Stories/components/StoriesLayout/StoriesLayout.tsx index 16e290c1..652346eb 100644 --- a/src/components/Stories/components/StoriesLayout/StoriesLayout.tsx +++ b/src/components/Stories/components/StoriesLayout/StoriesLayout.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import {Xmark} from '@gravity-ui/icons'; -import {Button, Icon, Link} from '@gravity-ui/uikit'; -import type {ButtonProps} from '@gravity-ui/uikit'; +import {ChevronLeft, ChevronRight, Xmark} from '@gravity-ui/icons'; +import {Button, Flex, Icon, Link, Text} from '@gravity-ui/uikit'; import {MediaRenderer} from '..'; import {block} from '../../../utils/cn'; import {i18n} from '../../i18n'; import type {StoriesItem} from '../../types'; +import {StoriesTextBlockStyle} from '../../types'; import './StoriesLayout.scss'; @@ -24,109 +24,143 @@ export type StoriesLayoutProps = { items: StoriesItem[]; storyIndex: number; indexType: IndexType; - handleButtonClose: ( event: MouseEvent | KeyboardEvent | React.MouseEvent, ) => void; handleGotoPrevious: () => void; handleGotoNext: () => void; - action?: ButtonProps; }; // StoriesGroup component also use it -export const StoriesLayout = (props: StoriesLayoutProps) => { - const currentStory = props.items[props.storyIndex]; +export const StoriesLayout = ({ + items, + indexType, + storyIndex, + handleButtonClose, + handleGotoNext, + handleGotoPrevious, +}: StoriesLayoutProps) => { + const currentStory = items[storyIndex]; return (
-
-
-
- {props.items.length > 1 && ( -
- - {props.storyIndex + 1} / {props.items.length} - -
- )} -
- {currentStory && ( - - {currentStory.title && ( -
{currentStory.title}
- )} - {currentStory.description && ( -
- {currentStory.description} -
- )} - {currentStory.url && ( -
- - {i18n('label_more')} - -
- )} -
- )} -
-
- {IndexType.Single === props.indexType ? ( - - ) : ( - - {IndexType.Start !== props.indexType && ( - - )} - {IndexType.InProccess !== props.indexType && ( - - )} - {IndexType.End !== props.indexType && ( - - )} - - )} - {props.action &&
-
-
- - {currentStory?.media && ( -
- -
+ {storyIndex + 1} / {items.length} + + )} +
+ {currentStory && ( + + {currentStory.title && ( + + {currentStory.title} + + )} + {currentStory.description && ( + + {currentStory.description} + + )} + {currentStory.url && ( +
+ + {i18n('label_more')} + +
+ )} + {Boolean(currentStory.firstAction || currentStory.secondAction) && ( + + {currentStory.firstAction && ( +
+
+ {currentStory?.media && ( + + )} +
+ + {indexType !== IndexType.Start && indexType !== IndexType.Single && ( + + )} + {indexType !== IndexType.End && indexType !== IndexType.Single && ( + + )}
); diff --git a/src/components/Stories/components/VideoView/VideoView.scss b/src/components/Stories/components/VideoView/VideoView.scss index 93064a34..4b3d5d15 100644 --- a/src/components/Stories/components/VideoView/VideoView.scss +++ b/src/components/Stories/components/VideoView/VideoView.scss @@ -3,8 +3,41 @@ $block: '.#{variables.$ns}stories-video-view'; #{$block} { - width: auto; + width: 100%; + height: 100%; max-width: 100%; max-height: 100%; - margin: auto; + + position: relative; + overflow: hidden; + + &_style_half-size { + border-radius: var(--g-spacing-5); + } + + &_style_half-size-with-margins { + width: calc(100% - 2 * var(--g-spacing-2)); + height: calc(100% - 2 * var(--g-spacing-2)); + + border-radius: var(--g-spacing-5); + margin: var(--g-spacing-2); + } + + &_style_full-size { + position: absolute; + inset-block-start: 0; + inset-inline-start: 0; + border-radius: var(--g-spacing-5); + + z-index: 0; + } + + &__video { + position: absolute; + width: 100%; + height: 100%; + object-fit: cover; + + z-index: 0; + } } diff --git a/src/components/Stories/components/VideoView/VideoView.tsx b/src/components/Stories/components/VideoView/VideoView.tsx index 26321f11..d973f5cb 100644 --- a/src/components/Stories/components/VideoView/VideoView.tsx +++ b/src/components/Stories/components/VideoView/VideoView.tsx @@ -1,29 +1,33 @@ import React from 'react'; import {block} from '../../../utils/cn'; -import type {StoriesItemMedia} from '../../types'; +import type {MediaRendererProps} from '../MediaRenderer/MediaRenderer'; import './VideoView.scss'; const b = block('stories-video-view'); -export interface VideoViewProps { - media: StoriesItemMedia; -} +export interface VideoViewProps extends MediaRendererProps {} + +export function VideoView({media, style}: VideoViewProps) { + if (media.type === 'video') { + return ( +
+
+ ); + } -export function VideoView({media}: VideoViewProps) { - return media.type === 'video' ? ( -