@os-design/website 1.0.201 → 1.0.202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@os-design/website",
3
- "version": "1.0.201",
3
+ "version": "1.0.202",
4
4
  "license": "UNLICENSED",
5
5
  "repository": "git@gitlab.com:os-team/libs/os-design.git",
6
6
  "main": "dist/cjs/index.js",
@@ -14,7 +14,8 @@
14
14
  "./package.json": "./package.json"
15
15
  },
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "src"
18
19
  ],
19
20
  "sideEffects": false,
20
21
  "scripts": {
@@ -46,5 +47,5 @@
46
47
  "react": ">=18",
47
48
  "react-dom": ">=18"
48
49
  },
49
- "gitHead": "bbd193f118a3128033d4d99ffcb4e96fe06f0dba"
50
+ "gitHead": "3c083af96a57f5694d51dcb5da1741773543bf77"
50
51
  }
@@ -0,0 +1,7 @@
1
+ import '@emotion/react';
2
+ import { Theme as BaseTheme } from '@os-design/theming';
3
+
4
+ declare module '@emotion/react' {
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
6
+ export interface Theme extends BaseTheme {}
7
+ }
@@ -0,0 +1,14 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import PageContent, { PageContentProps } from './index';
4
+
5
+ export default {
6
+ title: 'Website/PageContent',
7
+ component: PageContent,
8
+ } satisfies Meta<PageContentProps>;
9
+
10
+ export const ChildrenText: StoryObj<PageContentProps> = {
11
+ args: {
12
+ children: 'Text '.repeat(50).trimEnd(),
13
+ },
14
+ };
@@ -0,0 +1,16 @@
1
+ import styled from '@emotion/styled';
2
+ import {
3
+ PageContent as BasePageContent,
4
+ PageContentProps as BasePageContentProps,
5
+ } from '@os-design/core';
6
+
7
+ export type PageContentProps = BasePageContentProps;
8
+
9
+ /**
10
+ * The page content of the website page.
11
+ */
12
+ const PageContent = styled(BasePageContent)`
13
+ padding: 0 !important;
14
+ `;
15
+
16
+ export default PageContent;
@@ -0,0 +1,78 @@
1
+ import { ThemeOverrider } from '@os-design/theming';
2
+ import { Meta, StoryFn, StoryObj } from '@storybook/react';
3
+ import React from 'react';
4
+ import Section, { SectionProps } from './index';
5
+
6
+ const Component: React.FC<SectionProps> = (props) => (
7
+ <Section {...props}>
8
+ <h1>Title</h1>
9
+ <p>{'Text '.repeat(200).trimEnd()}</p>
10
+ </Section>
11
+ );
12
+
13
+ export default {
14
+ title: 'Website/Section',
15
+ component: Component,
16
+ args: {
17
+ bgProps: undefined,
18
+ },
19
+ } satisfies Meta<SectionProps>;
20
+
21
+ export const NoParameters: StoryObj<SectionProps> = {};
22
+
23
+ export const Small: StoryObj<SectionProps> = {
24
+ args: {
25
+ size: 'small',
26
+ },
27
+ };
28
+
29
+ export const Medium: StoryObj<SectionProps> = {
30
+ args: {
31
+ size: 'medium',
32
+ },
33
+ };
34
+
35
+ export const Large: StoryObj<SectionProps> = {
36
+ args: {
37
+ size: 'large',
38
+ },
39
+ };
40
+
41
+ export const CustomSize: StoryObj<SectionProps> = {
42
+ args: {
43
+ size: '2em',
44
+ },
45
+ };
46
+
47
+ export const BgUrl: StoryObj<SectionProps> = {
48
+ args: {
49
+ bgUrl:
50
+ 'https://storage.yandexcloud.net/englika/collections/Q29sbGVjdGlvbjoyMw-366c',
51
+ },
52
+ };
53
+
54
+ export const BgProps: StoryObj<SectionProps> = {
55
+ args: {
56
+ ...BgUrl.args,
57
+ bgProps: {
58
+ sizes: [72, 192],
59
+ defaultSize: 192,
60
+ },
61
+ },
62
+ };
63
+
64
+ export const LimitedWidth: StoryObj<SectionProps> = {
65
+ args: {
66
+ limitedWidth: true,
67
+ },
68
+ };
69
+
70
+ export const Centered: StoryObj<SectionProps> = {
71
+ args: {
72
+ centered: true,
73
+ },
74
+ };
75
+
76
+ export const Dark: StoryObj<SectionProps> = {
77
+ parameters: { theme: 'dark' },
78
+ };
@@ -0,0 +1,156 @@
1
+ import { css } from '@emotion/react';
2
+
3
+ import styled from '@emotion/styled';
4
+
5
+ import { Image, ImageProps } from '@os-design/core';
6
+
7
+ import { m } from '@os-design/media';
8
+ import { sizeStyles, WithSize } from '@os-design/styles';
9
+ import { clr, ThemeOverrider, useTheme } from '@os-design/theming';
10
+ import { omitEmotionProps } from '@os-design/utils';
11
+ import React, { forwardRef } from 'react';
12
+
13
+ type JsxSectionProps = Omit<JSX.IntrinsicElements['section'], 'ref'>;
14
+ export interface SectionProps extends JsxSectionProps, WithSize {
15
+ /**
16
+ * The URL of the background image.
17
+ * @default undefined
18
+ */
19
+ bgUrl?: string;
20
+ /**
21
+ * Props of the background image.
22
+ * @default undefined
23
+ */
24
+ bgProps?: Omit<ImageProps, 'url' | 'cover'>;
25
+ /**
26
+ * Whether the content has a limited width.
27
+ * @default false
28
+ */
29
+ limitedWidth?: boolean;
30
+ /**
31
+ * Whether the content is centered.
32
+ * @default false
33
+ */
34
+ centered?: boolean;
35
+ }
36
+
37
+ const BG_IMAGE_CLASS_NAME = 'section-bg-image';
38
+
39
+ const hasBgStyles = (p) =>
40
+ p.hasBg &&
41
+ css`
42
+ & > *:not(.${BG_IMAGE_CLASS_NAME}) {
43
+ z-index: 1;
44
+ }
45
+ `;
46
+
47
+ interface ContainerProps extends WithSize {
48
+ hasBg: boolean;
49
+ }
50
+ const Container = styled(
51
+ 'section',
52
+ omitEmotionProps('hasBg', 'size')
53
+ )<ContainerProps>`
54
+ position: relative;
55
+
56
+ display: flex;
57
+ flex-direction: column;
58
+ align-items: center;
59
+
60
+ padding: ${(p) => p.theme.sectionPaddingVertical}em
61
+ ${(p) => p.theme.sectionPaddingHorizontal[0]}em;
62
+ ${m.min.sm} {
63
+ padding: ${(p) => p.theme.sectionPaddingVertical}em
64
+ ${(p) => p.theme.sectionPaddingHorizontal[1]}em;
65
+ }
66
+
67
+ color: ${(p) => clr(p.theme.colorText)};
68
+
69
+ ${hasBgStyles};
70
+ ${sizeStyles};
71
+ `;
72
+
73
+ const BackgroundImage = styled(Image)`
74
+ // Reset image styles
75
+ border-radius: 0;
76
+
77
+ position: absolute;
78
+ top: 0;
79
+ left: 0;
80
+ width: 100%;
81
+ height: 100%;
82
+ filter: brightness(${(p) => p.theme.sectionBgImageBrightness}%);
83
+ `;
84
+
85
+ const centeredStyles = (p) =>
86
+ p.centered &&
87
+ css`
88
+ display: flex;
89
+ flex-direction: column;
90
+ align-items: center;
91
+ text-align: center;
92
+ `;
93
+
94
+ interface LimitedWidthProps {
95
+ centered: boolean;
96
+ }
97
+ const LimitedWidth = styled(
98
+ 'div',
99
+ omitEmotionProps('centered')
100
+ )<LimitedWidthProps>`
101
+ width: 100%;
102
+ max-width: ${(p) => p.theme.sectionMaxWidth}em;
103
+ ${centeredStyles};
104
+ `;
105
+
106
+ /**
107
+ * The base section for the content.
108
+ */
109
+ const Section = forwardRef<HTMLElement, SectionProps>(
110
+ (
111
+ {
112
+ bgUrl,
113
+ bgProps = { defaultSize: 1024 },
114
+ limitedWidth = false,
115
+ centered = false,
116
+ size,
117
+ children,
118
+ ...rest
119
+ },
120
+ ref
121
+ ) => {
122
+ const { className: bgClassName, ...restBgProps } = bgProps;
123
+ const { overrides } = useTheme();
124
+
125
+ const section = (
126
+ <Container hasBg={!!bgUrl} size={size} {...rest} ref={ref}>
127
+ {limitedWidth ? (
128
+ <LimitedWidth centered={centered}>{children}</LimitedWidth>
129
+ ) : (
130
+ children
131
+ )}
132
+ {bgUrl && (
133
+ <BackgroundImage
134
+ url={bgUrl}
135
+ cover
136
+ className={[bgClassName, BG_IMAGE_CLASS_NAME]
137
+ .filter((i) => i)
138
+ .join(' ')}
139
+ {...restBgProps}
140
+ />
141
+ )}
142
+ </Container>
143
+ );
144
+
145
+ if (!bgUrl) return section;
146
+ return (
147
+ <ThemeOverrider activeTheme='dark' overrides={overrides}>
148
+ {section}
149
+ </ThemeOverrider>
150
+ );
151
+ }
152
+ );
153
+
154
+ Section.displayName = 'Section';
155
+
156
+ export default Section;
@@ -0,0 +1,28 @@
1
+ import { ThemeOverrider } from '@os-design/theming';
2
+ import { Meta, StoryFn, StoryObj } from '@storybook/react';
3
+ import React from 'react';
4
+ import SectionFeature, { SectionFeatureProps } from './index';
5
+
6
+ const Component: React.FC<SectionFeatureProps> = (props) => (
7
+ <SectionFeature {...props}>
8
+ <h1>Title</h1>
9
+ <p>{'Text '.repeat(100).trimEnd()}</p>
10
+ </SectionFeature>
11
+ );
12
+
13
+ export default {
14
+ title: 'Website/SectionFeature',
15
+ component: Component,
16
+ } satisfies Meta<SectionFeatureProps>;
17
+
18
+ export const NoParameters: StoryObj<SectionFeatureProps> = {};
19
+
20
+ export const Media: StoryObj<SectionFeatureProps> = {
21
+ args: {
22
+ media: <img src='https://loremflickr.com/400/400' alt='media' />,
23
+ },
24
+ };
25
+
26
+ export const Dark: StoryObj<SectionFeatureProps> = {
27
+ parameters: { theme: 'dark' },
28
+ };
@@ -0,0 +1,111 @@
1
+ import { css } from '@emotion/react';
2
+
3
+ import styled from '@emotion/styled';
4
+
5
+ import { m } from '@os-design/media';
6
+ import { clr } from '@os-design/theming';
7
+ import { omitEmotionProps } from '@os-design/utils';
8
+ import React, { forwardRef } from 'react';
9
+
10
+ import Section, { SectionProps } from '../Section';
11
+
12
+ export interface SectionFeatureProps extends SectionProps {
13
+ /**
14
+ * The media component. For example, an image.
15
+ * @default undefined
16
+ */
17
+ media?: React.ReactElement;
18
+ }
19
+
20
+ const containerHasMediaStyles = (p) =>
21
+ p.hasMedia &&
22
+ css`
23
+ display: grid;
24
+
25
+ grid-template-columns: 1fr;
26
+ grid-template-rows: auto auto;
27
+ grid-template-areas: 'content' 'media';
28
+ grid-row-gap: ${p.theme.sectionFeatureRowGap}em;
29
+
30
+ ${m.min.md} {
31
+ grid-template-columns:
32
+ minmax(
33
+ ${100 - p.theme.sectionFeatureMediaWidthPercent}%,
34
+ ${p.theme.sectionFeatureContentMaxWidth}em
35
+ )
36
+ ${p.theme.sectionFeatureMediaWidthPercent}%;
37
+ grid-template-rows: 1fr;
38
+ grid-template-areas: 'content media';
39
+ grid-column-gap: ${p.theme.sectionFeatureColumnGapPercent}%;
40
+ padding-right: ${p.theme.sectionFeatureColumnGapPercent * 2}%;
41
+ }
42
+ `;
43
+
44
+ const containerNotHasMediaStyles = (p) =>
45
+ !p.hasMedia &&
46
+ css`
47
+ max-width: ${p.theme.sectionFeatureContentMaxWidth}em;
48
+ `;
49
+
50
+ interface ContainerProps {
51
+ hasMedia: boolean;
52
+ }
53
+ const Container = styled('div', omitEmotionProps('hasMedia'))<ContainerProps>`
54
+ color: ${(p) => clr(p.theme.colorText)};
55
+ ${containerHasMediaStyles};
56
+ ${containerNotHasMediaStyles};
57
+ `;
58
+
59
+ const Content = styled.div`
60
+ grid-area: content;
61
+ align-self: center;
62
+ `;
63
+
64
+ const Media = styled.div`
65
+ grid-area: media;
66
+ align-self: center;
67
+ `;
68
+
69
+ const sectionHasMediaStyles = (p) =>
70
+ p.hasMedia &&
71
+ css`
72
+ ${m.min.md} {
73
+ &:nth-of-type(even) {
74
+ & > div {
75
+ grid-template-areas: 'media content';
76
+ grid-template-columns: ${p.theme.sectionFeatureMediaWidthPercent}% minmax(
77
+ ${100 - p.theme.sectionFeatureMediaWidthPercent}%,
78
+ ${p.theme.sectionFeatureContentMaxWidth}em
79
+ );
80
+ }
81
+ }
82
+ }
83
+ `;
84
+
85
+ interface StyledSectionProps {
86
+ hasMedia: boolean;
87
+ }
88
+ const StyledSection = styled(
89
+ Section,
90
+ omitEmotionProps('hasMedia')
91
+ )<StyledSectionProps>`
92
+ ${sectionHasMediaStyles};
93
+ `;
94
+
95
+ /**
96
+ * The section for describing an app feature.
97
+ */
98
+ const SectionFeature = forwardRef<HTMLElement, SectionFeatureProps>(
99
+ ({ media, children, ...rest }, ref) => (
100
+ <StyledSection hasMedia={!!media} {...rest} ref={ref}>
101
+ <Container hasMedia={!!media}>
102
+ <Content>{children}</Content>
103
+ {media && <Media>{media}</Media>}
104
+ </Container>
105
+ </StyledSection>
106
+ )
107
+ );
108
+
109
+ SectionFeature.displayName = 'SectionFeature';
110
+
111
+ export default SectionFeature;
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { default as PageContent } from './PageContent';
2
+ export { default as Section } from './Section';
3
+ export { default as SectionFeature } from './SectionFeature';
4
+
5
+ export * from './PageContent';
6
+ export * from './Section';
7
+ export * from './SectionFeature';
@@ -0,0 +1,178 @@
1
+ import styled from '@emotion/styled';
2
+ import {
3
+ TagList as BaseTagList,
4
+ Button,
5
+ Image,
6
+ InputSearch,
7
+ Layout,
8
+ Link,
9
+ LogoLink,
10
+ Navigation,
11
+ NavigationItem,
12
+ TagLink,
13
+ ThemeSwitcher,
14
+ } from '@os-design/core';
15
+ import { Blog, Contacts, Home } from '@os-design/icons';
16
+ import { m } from '@os-design/media';
17
+ import { Meta, StoryFn } from '@storybook/react';
18
+ import React from 'react';
19
+ import PageContent from './PageContent';
20
+ import Section from './Section';
21
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
22
+ // @ts-ignore
23
+ // eslint-disable-next-line import/no-relative-packages
24
+ import logo from '../../core/src/LogoLink/logo.example.svg';
25
+ import SectionFeature from './SectionFeature';
26
+
27
+ export default {
28
+ title: 'Website/samples',
29
+ parameters: {
30
+ layout: 'fullscreen',
31
+ docs: { page: null },
32
+ },
33
+ } as Meta;
34
+
35
+ const NavigationAddon = styled.div`
36
+ width: 100%;
37
+ height: ${(p) => p.theme.navigationSideWidth}em;
38
+ min-height: ${(p) => p.theme.navigationSideWidth}em;
39
+ display: flex;
40
+ justify-content: center;
41
+ align-items: center;
42
+ `;
43
+
44
+ const Header = styled.div`
45
+ display: flex;
46
+ justify-content: center;
47
+
48
+ padding: 1.5em ${(p) => p.theme.sectionPaddingHorizontal[0]}em;
49
+ ${m.min.sm} {
50
+ padding: 1.5em ${(p) => p.theme.sectionPaddingHorizontal[1]}em;
51
+ }
52
+ `;
53
+
54
+ const Footer = styled.footer`
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+
59
+ padding: 1.5em ${(p) => p.theme.sectionPaddingHorizontal[0]}em;
60
+ ${m.min.sm} {
61
+ padding: 1.5em ${(p) => p.theme.sectionPaddingHorizontal[1]}em;
62
+ }
63
+ `;
64
+
65
+ const SearchContent = styled.div`
66
+ width: 100%;
67
+ max-width: 40em;
68
+ text-align: center;
69
+ `;
70
+
71
+ const TagList = styled(BaseTagList)`
72
+ margin-top: 0.5em;
73
+ `;
74
+
75
+ export const HomePage: StoryFn = () => (
76
+ <Layout>
77
+ <Navigation
78
+ sideBottom={
79
+ <NavigationAddon>
80
+ <ThemeSwitcher />
81
+ </NavigationAddon>
82
+ }
83
+ >
84
+ <NavigationItem icon={<Home />}>Home</NavigationItem>
85
+ <NavigationItem icon={<Blog />}>Projects</NavigationItem>
86
+ <NavigationItem icon={<Contacts />}>Contacts</NavigationItem>
87
+ </Navigation>
88
+
89
+ <PageContent hasNavigation>
90
+ <Header>
91
+ <LogoLink src={logo} />
92
+ </Header>
93
+
94
+ <SectionFeature
95
+ media={
96
+ <Image url='https://storage.yandexcloud.net/englika/collections/Q29sbGVjdGlvbjoyMw-366c' />
97
+ }
98
+ >
99
+ <h1>Grow your business faster</h1>
100
+ <p>{'Text '.repeat(50).trimEnd()}</p>
101
+ <Button>Start</Button>
102
+ </SectionFeature>
103
+
104
+ <SectionFeature
105
+ media={
106
+ <Image url='https://storage.yandexcloud.net/englika/collections/Q29sbGVjdGlvbjoyMw-366c' />
107
+ }
108
+ >
109
+ <h2>Super fast</h2>
110
+ <p>{'Text '.repeat(50).trimEnd()}</p>
111
+ </SectionFeature>
112
+
113
+ <SectionFeature
114
+ media={
115
+ <Image url='https://storage.yandexcloud.net/englika/collections/Q29sbGVjdGlvbjoyMw-366c' />
116
+ }
117
+ >
118
+ <h2>Completely secure</h2>
119
+ <p>{'Text '.repeat(50).trimEnd()}</p>
120
+ </SectionFeature>
121
+
122
+ <Section>
123
+ <h2>Try it for free</h2>
124
+ <p>{'Text '.repeat(200).trimEnd()}</p>
125
+ <Button>Start</Button>
126
+ </Section>
127
+
128
+ <Footer>
129
+ <Link href='mailto:company@domain.com'>company@domain.com</Link>
130
+ <div>© {new Date().getFullYear()} company</div>
131
+ </Footer>
132
+ </PageContent>
133
+ </Layout>
134
+ );
135
+
136
+ export const BlogPage: StoryFn = () => (
137
+ <Layout>
138
+ <Navigation
139
+ sideBottom={
140
+ <NavigationAddon>
141
+ <ThemeSwitcher />
142
+ </NavigationAddon>
143
+ }
144
+ >
145
+ <NavigationItem icon={<Home />}>Home</NavigationItem>
146
+ <NavigationItem icon={<Blog />}>Projects</NavigationItem>
147
+ <NavigationItem icon={<Contacts />}>Contacts</NavigationItem>
148
+ </Navigation>
149
+
150
+ <PageContent hasNavigation>
151
+ <Header>
152
+ <LogoLink src={logo} />
153
+ </Header>
154
+
155
+ <Section>
156
+ <SearchContent>
157
+ <h1>Blog posts</h1>
158
+ <InputSearch placeholder='Search' />
159
+ <TagList collapsible>
160
+ <TagLink>Tag 1</TagLink>
161
+ <TagLink>Tag 2</TagLink>
162
+ </TagList>
163
+ </SearchContent>
164
+ </Section>
165
+
166
+ <Section>
167
+ <h2>Try it for free</h2>
168
+ <p>{'Text '.repeat(200).trimEnd()}</p>
169
+ <Button>Start</Button>
170
+ </Section>
171
+
172
+ <Footer>
173
+ <Link href='mailto:company@domain.com'>company@domain.com</Link>
174
+ <div>© {new Date().getFullYear()} company</div>
175
+ </Footer>
176
+ </PageContent>
177
+ </Layout>
178
+ );