Skip to content

Commit

Permalink
feat: add feedback issues to themer (#289)
Browse files Browse the repository at this point in the history
* feat: add feedback issues to themer

* fix: another adaptive style fixes

* feat: add sticky tabs for themer page

* feat: add export sheet instead of modal in mobile view

* fix: some styles
  • Loading branch information
bityutskiyAO authored Nov 7, 2024
1 parent da064f3 commit 99e165d
Show file tree
Hide file tree
Showing 21 changed files with 513 additions and 175 deletions.
3 changes: 3 additions & 0 deletions public/locales/en/themes.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"is_everything_set": "Is everything set?",
"cancel": "Cancel",
"export_theme_config": "Export theme config",
"export_theme_cancel_btn": "Cancel",
"export_theme_apply_btn": "Save custom.svs",
"export_theme_apply-theme-alert-title": "How to apply the theme",
"radius": "Radius",
"radius_regular": "Regular",
"radius_circled": "Circled",
Expand Down
3 changes: 3 additions & 0 deletions public/locales/ru/themes.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"is_everything_set": "Все настроили?",
"cancel": "Закрыть",
"export_theme_config": "Экспорт конфигурации темы",
"export_theme_cancel_btn": "Закрыть",
"export_theme_apply_btn": "Сохранить custom.svs",
"export_theme_apply-theme-alert-title": "Как применить выбранную тему",
"radius": "Радиус",
"radius_regular": "Стандартный",
"radius_circled": "Круглый",
Expand Down
84 changes: 66 additions & 18 deletions src/components/Themes/Themes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,40 @@

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

@mixin sticky() {
z-index: 150;
padding-block: var(--g-spacing-2);
background-color: var(--g-color-base-float);
border-bottom: 1px solid var(--g-color-line-generic);
}

@mixin gridContainerOffsets() {
max-width: calc(1232px + 16px + 80px);
padding-inline: var(--g-spacing-10);
margin-inline: auto;
}

#{$block} {
position: relative;
z-index: 1;
margin-block-start: calc(var(--g-spacing-base) * 16);
&__grid {
position: relative;
z-index: 1;

&__content {
padding: calc(var(--g-spacing-base) * 12) 0;

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm')) {
padding: calc(var(--g-spacing-base) * 6) 0;
}
}
}

&__title {
@include gridContainerOffsets();
margin-block-start: calc(var(--g-spacing-base) * 12);
margin-block-end: var(--g-spacing-6);
@media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm')) {
padding-inline: var(--g-spacing-4);
}

&__text {
@include ukitMixins.text-display-4();
Expand All @@ -21,17 +48,49 @@ $block: '.#{variables.$ns}themes';
}
}

&__header-actions {
&__header-actions-wrapper {
position: sticky;
top: 0;

margin-block-end: calc(var(--g-spacing-base) * 8);

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'md')) {
margin-block-end: 0;
}

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm')) {
padding-inline: var(--g-spacing-4);
}

&_sticky {
@include sticky();
}
}

&__header-actions {
justify-content: space-between;

@include gridContainerOffsets();
@media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) {
flex-direction: column;
justify-content: flex-start;
}

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm')) {
margin: 0 -16px;
padding-inline: 16px;
}
}

&__header-action-buttons {
padding-inline: 40px;

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm')) {
padding-inline: 16px;
}
}

& &__tabs {
&__tags {
display: flex;
overflow: auto;
flex-wrap: wrap;
Expand All @@ -46,26 +105,15 @@ $block: '.#{variables.$ns}themes';

&__export-theme-btn {
--g-button-border-radius: 8px;
border-radius: var(--g-border-radius-m);
width: fit-content;

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) {
margin-top: var(--g-spacing-6);
margin-block: var(--g-spacing-6) var(--g-spacing-8);
}

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm')) {
width: 100%;
}
}

&__export-theme-btn {
border-radius: var(--g-border-radius-m);
}

&__content {
padding: calc(var(--g-spacing-base) * 12) 0;

@media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm')) {
padding: calc(var(--g-spacing-base) * 6) 0;
}
}
}
84 changes: 61 additions & 23 deletions src/components/Themes/Themes.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {Col, Grid, Row} from '@gravity-ui/page-constructor';
import {BREAKPOINTS, Grid, useWindowBreakpoint} from '@gravity-ui/page-constructor';
import {ArrowUpFromSquare} from 'landing-icons';
import {Button, Flex, Icon, Text} from 'landing-uikit';
import {useTranslation} from 'next-i18next';
import React, {useMemo, useState} from 'react';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {ThemeExport} from 'src/components/Themes/ui/ThemeExport/ThemeExport';

import {block} from '../../utils';
import {TagItem, Tags} from '../Tags/Tags';
Expand All @@ -13,7 +14,6 @@ import {BorderRadiusTab} from './ui/BorderRadiusTab/BorderRadiusTab';
import {ColorsTab} from './ui/ColorsTab/ColorsTab';
import {PreviewTab} from './ui/PreviewTab/PreviewTab';
import {ThemeCreatorContextProvider} from './ui/ThemeCreatorContextProvider';
import {ThemeExportDialog} from './ui/ThemeExportDialog/ThemeExportDialog';
import {TypographyTab} from './ui/TypographyTab/TypographyTab';

const b = block('themes');
Expand All @@ -34,6 +34,9 @@ const tabToComponent: Record<ThemeTab, React.ComponentType | undefined> = {

export const Themes = () => {
const {t} = useTranslation('themes');
const breakpoint = useWindowBreakpoint();

const headerRef = useRef<HTMLDivElement>(null);

const [isExportDialogVisible, toggleExportDialog] = React.useReducer(
(isOpen) => !isOpen,
Expand Down Expand Up @@ -62,41 +65,76 @@ export const Themes = () => {
[t],
);

useEffect(() => {
const contentEl = document.getElementsByClassName(
'gravity-ui-landing-layout__wrapper',
)?.[0];

if (!contentEl) {
return;
}

const onScroll = () => {
if (headerRef?.current?.offsetTop && headerRef.current.offsetTop > 136) {
headerRef.current?.classList.add(b('header-actions-wrapper_sticky'));
} else {
headerRef.current?.classList.remove(b('header-actions-wrapper_sticky'));
}
};

contentEl.addEventListener('scroll', onScroll);

return () => {
contentEl.removeEventListener('scroll', onScroll);
};
}, []);

const [activeTab, setActiveTab] = useState<ThemeTab>(ThemeTab.Colors);

const TabComponent = tabToComponent[activeTab];

const ExportBtn = useCallback(
() => (
<Button
className={b('export-theme-btn')}
view="action"
size="xl"
onClick={toggleExportDialog}
>
<Icon data={ArrowUpFromSquare} />
<Text>{t('btn_export_theme')}</Text>
</Button>
),
[toggleExportDialog],
);

return (
<ThemeCreatorContextProvider initialTheme={DEFAULT_THEME}>
<Grid className={b()}>
<Row className={b('title')}>
<Col sizes={12}>
<Text className={b('title__text')}>{t('title')}</Text>
</Col>
</Row>
<div className={b('title')}>
<Text className={b('title__text')}>{t('title')}</Text>
</div>
<div className={b('header-actions-wrapper')} ref={headerRef}>
<Flex className={b('header-actions')}>
<Tags
className={b('tabs')}
className={b('tags')}
items={tags}
value={activeTab}
onChange={setActiveTab}
/>
<Button
className={b('export-theme-btn')}
view="action"
size="xl"
onClick={toggleExportDialog}
>
<Icon data={ArrowUpFromSquare} />
<Text>{t('btn_export_theme')}</Text>
</Button>
{breakpoint >= BREAKPOINTS.md && <ExportBtn />}
</Flex>
</div>

<Row className={b('content')}>
<Col sizes={12}>{TabComponent ? <TabComponent /> : null}</Col>
</Row>
{breakpoint < BREAKPOINTS.md && (
<div className={b('header-action-buttons')}>
<ExportBtn />
</div>
)}
<Grid className={b('grid')}>
<div className={b('grid__content')}>{TabComponent ? <TabComponent /> : null}</div>
</Grid>
<ThemeExportDialog isOpen={isExportDialogVisible} onClose={toggleExportDialog} />

<ThemeExport isOpen={isExportDialogVisible} onClose={toggleExportDialog} />
</ThemeCreatorContextProvider>
);
};
14 changes: 7 additions & 7 deletions src/components/Themes/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const DEFAULT_COLORS: ThemeOptions['colors'] = {
'text-brand-heavy': 'private.brand.700-solid',
'text-brand-contrast': TEXT_CONTRAST_COLORS.light.black,
'text-link': 'private.brand.600-solid',
'text-link-hover': 'private.orange.800-solid',
'text-link-hover': 'private.brand.800-solid',
'text-link-visited': 'private.purple.550-solid',
'text-link-visited-hover': 'private.purple.800-solid',
},
Expand Down Expand Up @@ -145,7 +145,7 @@ export const BRAND_COLORS_PRESETS: BrandPreset[] = [
'text-brand-heavy': 'private.brand.700-solid',
'text-brand-contrast': TEXT_CONTRAST_COLORS.light.black,
'text-link': 'private.brand.600-solid',
'text-link-hover': 'private.orange.800-solid',
'text-link-hover': 'private.brand.800-solid',
'text-link-visited': 'private.purple.550-solid',
'text-link-visited-hover': 'private.purple.800-solid',
},
Expand Down Expand Up @@ -178,7 +178,7 @@ export const BRAND_COLORS_PRESETS: BrandPreset[] = [
'text-brand-heavy': 'private.brand.700-solid',
'text-brand-contrast': TEXT_CONTRAST_COLORS.light.white,
'text-link': 'private.brand.600-solid',
'text-link-hover': 'private.orange.800-solid',
'text-link-hover': 'private.brand.800-solid',
'text-link-visited': 'private.purple.550-solid',
'text-link-visited-hover': 'private.purple.800-solid',
},
Expand Down Expand Up @@ -211,7 +211,7 @@ export const BRAND_COLORS_PRESETS: BrandPreset[] = [
'text-brand-heavy': 'private.brand.700-solid',
'text-brand-contrast': TEXT_CONTRAST_COLORS.light.white,
'text-link': 'private.brand.600-solid',
'text-link-hover': 'private.orange.800-solid',
'text-link-hover': 'private.brand.800-solid',
'text-link-visited': 'private.purple.550-solid',
'text-link-visited-hover': 'private.purple.800-solid',
},
Expand Down Expand Up @@ -244,7 +244,7 @@ export const BRAND_COLORS_PRESETS: BrandPreset[] = [
'text-brand-heavy': 'private.brand.700-solid',
'text-brand-contrast': TEXT_CONTRAST_COLORS.light.white,
'text-link': 'private.brand.600-solid',
'text-link-hover': 'private.orange.800-solid',
'text-link-hover': 'private.brand.800-solid',
'text-link-visited': 'private.purple.550-solid',
'text-link-visited-hover': 'private.purple.800-solid',
},
Expand Down Expand Up @@ -277,7 +277,7 @@ export const BRAND_COLORS_PRESETS: BrandPreset[] = [
'text-brand-heavy': 'private.brand.700-solid',
'text-brand-contrast': TEXT_CONTRAST_COLORS.light.black,
'text-link': 'private.brand.600-solid',
'text-link-hover': 'private.orange.800-solid',
'text-link-hover': 'private.brand.800-solid',
'text-link-visited': 'private.purple.550-solid',
'text-link-visited-hover': 'private.purple.800-solid',
},
Expand Down Expand Up @@ -310,7 +310,7 @@ export const BRAND_COLORS_PRESETS: BrandPreset[] = [
'text-brand-heavy': 'private.brand.700-solid',
'text-brand-contrast': TEXT_CONTRAST_COLORS.light.white,
'text-link': 'private.brand.600-solid',
'text-link-hover': 'private.orange.800-solid',
'text-link-hover': 'private.brand.800-solid',
'text-link-visited': 'private.purple.550-solid',
'text-link-visited-hover': 'private.purple.800-solid',
},
Expand Down
10 changes: 10 additions & 0 deletions src/components/Themes/lib/themeCreatorExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ ${FONTS_TEMPLATE_NAME}
}
`.trim();

export const APPLY_THEME_TEMPLATE = `
Create custom.scss file with the styles created in the Themer (from the section below) and import it after the default UIKit styles.
// Import default UIKit styles
import '@gravity-ui/uikit/styles/styles.css';
// Styles from the Themer
import './custom.scss';
`;

export type ExportFormat = 'scss' | 'json';

type ExportThemeParams = {
Expand Down
10 changes: 10 additions & 0 deletions src/components/Themes/ui/ColorPickerInput/ColorPickerInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ export const ColorPickerInput = ({
validateAndChangeExternal(inputValue);
}, [inputValue, validateAndChangeExternal]);

const onKeyPress = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
validateAndChangeExternal(inputValue);
}
},
[inputValue, validateAndChangeExternal],
);

useEffect(() => {
// Dont validate if not initial value
if (!value && !defaultValue) {
Expand All @@ -109,6 +118,7 @@ export const ColorPickerInput = ({
className={b('text-input')}
name={name}
value={inputValue}
onKeyPress={onKeyPress}
errorPlacement="inside"
errorMessage={errorMessage || t('color-input_validation-format-error')}
validationState={validationError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {useTranslation} from 'next-i18next';
import React from 'react';

import {block} from '../../../../utils';
import {ThemeExportDialog} from '../ThemeExportDialog/ThemeExportDialog';
import {ThemeExport} from '../ThemeExport/ThemeExport';
import {ThemeSection} from '../ThemeSection';

import './ExportThemeSection.scss';
Expand All @@ -21,7 +21,7 @@ export const ExportThemeSection = () => {
<Icon data={ArrowUpFromSquare} />
{t('btn_export_theme')}
</Button>
<ThemeExportDialog isOpen={isDialogVisible} onClose={toggleDialog} />
<ThemeExport isOpen={isDialogVisible} onClose={toggleDialog} />
</ThemeSection>
);
};
Loading

0 comments on commit 99e165d

Please sign in to comment.