lupine.press 1.0.0
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/README.md +79 -0
- package/package.json +18 -0
- package/src/components/index.ts +7 -0
- package/src/components/lang-switcher.tsx +38 -0
- package/src/components/press-content.tsx +146 -0
- package/src/components/press-header.tsx +80 -0
- package/src/components/press-heading.tsx +46 -0
- package/src/components/press-home.tsx +109 -0
- package/src/components/press-layout.tsx +46 -0
- package/src/components/press-sidemenu.tsx +74 -0
- package/src/frames/index.ts +1 -0
- package/src/frames/press-frame.tsx +163 -0
- package/src/index.ts +7 -0
- package/src/page/index.ts +1 -0
- package/src/page/press-page.tsx +73 -0
- package/src/services/cache.ts +18 -0
- package/src/services/index.ts +3 -0
- package/src/services/markdown.ts +18 -0
- package/src/services/press-load.ts +17 -0
- package/src/styles/github.svg +3 -0
- package/src/styles/global.css +496 -0
- package/src/styles/lang.svg +3 -0
- package/src/styles/menu.svg +3 -0
- package/src/styles/press-themes.ts +14 -0
- package/src/styles/theme.svg +9 -0
- package/tsconfig.json +113 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# lupine.press
|
|
2
|
+
|
|
3
|
+
`lupine.press` is a lightweight, high-performance documentation site framework built on top of `lupine.web`. It provides a complete solution for rendering Markdown-based documentation websites with a responsive layout, sidebar navigation, and theming support.
|
|
4
|
+
|
|
5
|
+
It is designed to work seamlessly with the `lupine` ecosystem, powering documentation sites like the official LupineJS documentation.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Responsive Layout**: Built-in `PressFrame` provides a standard documentation layout with a header, responsive sidebar, and content area.
|
|
10
|
+
- **Markdown Rendering**: Optimized for rendering content generated from Markdown files, including syntax highlighting and standard typography.
|
|
11
|
+
- **Sidebar Navigation**: Automatically generates a multi-level sidebar based on your configuration.
|
|
12
|
+
- **Theming**: Built-in support for multiple themes (e.g., light/dark mode) via `lupine.components` theme system.
|
|
13
|
+
- **Routing**: explicit integration with `PageRouter` for handling client-side navigation.
|
|
14
|
+
- **多语言支持**:自动扫描多语言目录的 markdown 文件,多语言显示切换。
|
|
15
|
+
|
|
16
|
+
## Usage Guide
|
|
17
|
+
|
|
18
|
+
To use `lupine.press`, you typically set up a `lupine.web` application and configure it to use `PressPage` as the main route handler.
|
|
19
|
+
|
|
20
|
+
### 1. Prerequisites
|
|
21
|
+
|
|
22
|
+
Ensure you have `lupine.web` and `lupine.components` installed in your project.
|
|
23
|
+
|
|
24
|
+
### 2. Basic Setup
|
|
25
|
+
|
|
26
|
+
In your application entry point (e.g., `src/index.tsx`), you need to bind the necessary configurations and set up the router.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { bindRouter, PageRouter, bindTheme, bindLang, setDefaultPageTitle } from 'lupine.components';
|
|
30
|
+
import { bindPressData, PressPage, pressThemes } from 'lupine.press';
|
|
31
|
+
import { markdownConfig } from './markdown-config'; // Your generated markdown data
|
|
32
|
+
|
|
33
|
+
// 1. Initialize core settings
|
|
34
|
+
bindLang('en', {}); // Set default language
|
|
35
|
+
bindTheme('light', pressThemes); // Bind themes (includes specific styles for press)
|
|
36
|
+
setDefaultPageTitle('My Documentation');
|
|
37
|
+
|
|
38
|
+
// 2. Bind documentation data
|
|
39
|
+
// markdownConfig is a dictionary containing HTML content and metadata generated from markdown files.
|
|
40
|
+
bindPressData(markdownConfig);
|
|
41
|
+
|
|
42
|
+
// 3. Configure Router
|
|
43
|
+
const pageRouter = new PageRouter();
|
|
44
|
+
// Route all requests to PressPage, which handles looking up content in markdownConfig
|
|
45
|
+
pageRouter.use('*', PressPage);
|
|
46
|
+
|
|
47
|
+
// 4. Start the application
|
|
48
|
+
bindRouter(pageRouter);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Data Structure (`markdownConfig`)
|
|
52
|
+
|
|
53
|
+
The `bindPressData` function expects a configuration object where keys are route paths (e.g., `/guide/started`) and values contain the content and metadata.
|
|
54
|
+
|
|
55
|
+
Typically, this data is generated at build time from your Markdown files.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
export const markdownConfig = {
|
|
59
|
+
'/en/guide/started': {
|
|
60
|
+
html: '<h1>Getting Started</h1><p>...</p>', // Pre-rendered HTML content
|
|
61
|
+
data: {
|
|
62
|
+
title: 'Getting Started',
|
|
63
|
+
sidebar: [
|
|
64
|
+
// Sidebar configuration for this page context
|
|
65
|
+
{ type: 'group', text: 'Guide', level: 0 },
|
|
66
|
+
{ type: 'link', text: 'Installation', link: '/en/guide/install', level: 1 },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
headings: [{ level: 2, text: 'Prerequisites', id: 'prerequisites' }],
|
|
70
|
+
},
|
|
71
|
+
// ... other pages
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Architecture
|
|
76
|
+
|
|
77
|
+
- **`PressFrame`**: The main layout component. It handles the specific CSS and structure for a documentation site, ensuring the sidebar and content area scroll independently.
|
|
78
|
+
- **`PressPage`**: The "controller" component. It looks up the current URL in the bound `markdownConfig`, retrieves the corresponding HTML and metadata, and renders the `PressFrame` with the correct sidebar and content.
|
|
79
|
+
- **`pressLoad`**: A navigation utility to handle link clicks within the documentation, ensuring smooth client-side transitions.
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lupine.press",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A modern documentation generator for lupine.js",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"note": "echo 'build is not needed as the typescript code is supposed to be referred directly from other projects'",
|
|
9
|
+
"npm-publish": "npm publish --access public"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"lupine.web": "^1.0.0",
|
|
13
|
+
"lupine.components": "^1.0.0",
|
|
14
|
+
"lupine.api": "^1.0.0",
|
|
15
|
+
"marked": "^15.0.0",
|
|
16
|
+
"gray-matter": "^4.0.3"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { initializePage } from 'lupine.web';
|
|
2
|
+
import { PopupMenu } from 'lupine.components';
|
|
3
|
+
import langIcon from '../styles/lang.svg';
|
|
4
|
+
import { Svg } from 'lupine.components';
|
|
5
|
+
|
|
6
|
+
export const LangSwitcher = (props: { className?: string; currentLang: string; langs: any[] }) => {
|
|
7
|
+
const langs = props.langs || [];
|
|
8
|
+
const currentLabel = langs.find((l) => l.id === props.currentLang)?.text || 'Language';
|
|
9
|
+
|
|
10
|
+
const handleSelected = (text: string) => {
|
|
11
|
+
const lang = langs.find((l) => l.text === text);
|
|
12
|
+
if (lang && lang.id !== props.currentLang) {
|
|
13
|
+
let newPath = window.location.pathname;
|
|
14
|
+
const langIds = langs.map((l) => l.id).join('|');
|
|
15
|
+
const langRegex = new RegExp(`^/(${langIds})(\\/|$)`);
|
|
16
|
+
|
|
17
|
+
if (langRegex.test(newPath)) {
|
|
18
|
+
newPath = newPath.replace(langRegex, `/${lang.id}$2`);
|
|
19
|
+
} else {
|
|
20
|
+
newPath = `/${lang.id}${newPath === '/' ? '/' : newPath}`;
|
|
21
|
+
}
|
|
22
|
+
// window.location.href = newPath;
|
|
23
|
+
initializePage(newPath);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div class={['lang-switcher', props.className].join(' ')}>
|
|
29
|
+
<PopupMenu
|
|
30
|
+
list={langs.map((l) => l.text)}
|
|
31
|
+
defaultValue={currentLabel}
|
|
32
|
+
icon={<Svg>{langIcon}</Svg>}
|
|
33
|
+
handleSelected={handleSelected}
|
|
34
|
+
align='right'
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { CssProps, MediaQueryRange, MenuItemProps, PopupMenu, Svg, VNode } from 'lupine.components';
|
|
2
|
+
import { pressLoad } from '../services/press-load';
|
|
3
|
+
import { LayoutHome } from './press-home';
|
|
4
|
+
import { PageHeading } from './press-heading';
|
|
5
|
+
import menuIcon from '../styles/menu.svg';
|
|
6
|
+
|
|
7
|
+
export const PressContent = (props: {
|
|
8
|
+
children: VNode<any>;
|
|
9
|
+
isHome: boolean;
|
|
10
|
+
sidebar: any[];
|
|
11
|
+
headings: any[];
|
|
12
|
+
data: any;
|
|
13
|
+
}) => {
|
|
14
|
+
const css: CssProps = {
|
|
15
|
+
display: 'flex',
|
|
16
|
+
flex: 1,
|
|
17
|
+
maxWidth: '100vw',
|
|
18
|
+
margin: '0 auto',
|
|
19
|
+
width: '100%',
|
|
20
|
+
'.press-content': {
|
|
21
|
+
flex: 1,
|
|
22
|
+
padding: props.isHome ? '0' : '2rem 4rem',
|
|
23
|
+
width: '100%',
|
|
24
|
+
// maxWidth: isHome ? '100%' : '800px',
|
|
25
|
+
margin: props.isHome ? '0' : '0 auto',
|
|
26
|
+
minWidth: 0,
|
|
27
|
+
},
|
|
28
|
+
'.page-heading-container': {
|
|
29
|
+
width: '240px',
|
|
30
|
+
minWidth: '240px',
|
|
31
|
+
padding: '2rem 1rem',
|
|
32
|
+
position: 'sticky',
|
|
33
|
+
top: '64px',
|
|
34
|
+
maxHeight: 'calc(100vh - 64px)',
|
|
35
|
+
overflowY: 'auto',
|
|
36
|
+
alignSelf: 'flex-start', // Prevent stretching to full height
|
|
37
|
+
display: props.isHome || props.headings.length === 0 ? 'none' : 'block',
|
|
38
|
+
},
|
|
39
|
+
'.markdown-body': {
|
|
40
|
+
lineHeight: 1.6,
|
|
41
|
+
},
|
|
42
|
+
'.press-mobile-toc': {
|
|
43
|
+
display: 'none',
|
|
44
|
+
// border: '1px solid var(--press-border-color)',
|
|
45
|
+
borderRadius: '6px',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
padding: '4px',
|
|
48
|
+
position: 'fixed',
|
|
49
|
+
top: '74px',
|
|
50
|
+
right: '7px',
|
|
51
|
+
zIndex: 90,
|
|
52
|
+
fontSize: '0.9rem',
|
|
53
|
+
cursor: 'pointer',
|
|
54
|
+
textTransform: 'uppercase',
|
|
55
|
+
backgroundColor: 'var(--primary-bg-color)',
|
|
56
|
+
},
|
|
57
|
+
'.press-mobile-sidebar': {
|
|
58
|
+
display: 'none',
|
|
59
|
+
borderRadius: '6px',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
padding: '4px',
|
|
62
|
+
position: 'fixed',
|
|
63
|
+
top: '74px',
|
|
64
|
+
left: '7px',
|
|
65
|
+
zIndex: 90,
|
|
66
|
+
fontSize: '0.9rem',
|
|
67
|
+
cursor: 'pointer',
|
|
68
|
+
// can't put up level, otherwise it will override parent's same level selector
|
|
69
|
+
[MediaQueryRange.TabletBelow]: {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
[MediaQueryRange.MobileBelow]: {
|
|
74
|
+
'.page-heading-container': {
|
|
75
|
+
display: 'none',
|
|
76
|
+
},
|
|
77
|
+
'.press-mobile-toc': { display: 'flex' },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
return (
|
|
81
|
+
<main css={css}>
|
|
82
|
+
{!props.isHome && props.sidebar.length > 0 && (
|
|
83
|
+
<div class='press-mobile-sidebar'>
|
|
84
|
+
<PopupMenu
|
|
85
|
+
list={props.sidebar.map(
|
|
86
|
+
(item: any) =>
|
|
87
|
+
({
|
|
88
|
+
text: item.text,
|
|
89
|
+
id: item.link || '',
|
|
90
|
+
url: item.link || '',
|
|
91
|
+
indent: item.level,
|
|
92
|
+
visible: item.type ? true : false, // Validating it's processed item
|
|
93
|
+
disabled: item.type === 'group',
|
|
94
|
+
bold: item.type === 'group',
|
|
95
|
+
} as MenuItemProps)
|
|
96
|
+
)}
|
|
97
|
+
defaultValue='Menu'
|
|
98
|
+
tips=''
|
|
99
|
+
width='max-content'
|
|
100
|
+
maxHeight='400px'
|
|
101
|
+
align='left'
|
|
102
|
+
handleSelected={(text: string, item: any) => {
|
|
103
|
+
if (item && item.url) pressLoad(item.url);
|
|
104
|
+
}}
|
|
105
|
+
noUpdateLabel={true}
|
|
106
|
+
icon={<Svg>{menuIcon}</Svg>}
|
|
107
|
+
></PopupMenu>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<main class='press-content'>
|
|
112
|
+
{props.isHome ? <LayoutHome data={props.data} /> : <article class='markdown-body'>{props.children}</article>}
|
|
113
|
+
</main>
|
|
114
|
+
<aside class='page-heading-container'>
|
|
115
|
+
<PageHeading headings={props.headings} />
|
|
116
|
+
</aside>
|
|
117
|
+
{!props.isHome && props.headings.length > 0 && (
|
|
118
|
+
<div class='press-mobile-toc'>
|
|
119
|
+
<PopupMenu
|
|
120
|
+
list={props.headings.map(
|
|
121
|
+
(h) =>
|
|
122
|
+
({
|
|
123
|
+
text: h.text,
|
|
124
|
+
id: h.id,
|
|
125
|
+
indent: h.level - 2,
|
|
126
|
+
} as MenuItemProps)
|
|
127
|
+
)}
|
|
128
|
+
defaultValue='On this page'
|
|
129
|
+
tips=''
|
|
130
|
+
width='max-content'
|
|
131
|
+
maxHeight='300px'
|
|
132
|
+
align='right'
|
|
133
|
+
handleSelected={(text: string) => {
|
|
134
|
+
const cleanText = text.trim();
|
|
135
|
+
const heading = props.headings.find((h) => h.text === cleanText);
|
|
136
|
+
if (heading) {
|
|
137
|
+
document.getElementById(heading.id)?.scrollIntoView(true);
|
|
138
|
+
}
|
|
139
|
+
}}
|
|
140
|
+
noUpdateLabel={true}
|
|
141
|
+
></PopupMenu>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</main>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { CssProps, Svg, ThemeSelector } from 'lupine.components';
|
|
2
|
+
import { LangSwitcher } from './lang-switcher';
|
|
3
|
+
import githubIcon from '../styles/github.svg';
|
|
4
|
+
import themeIcon from '../styles/theme.svg';
|
|
5
|
+
|
|
6
|
+
export type PageHeaderProps = {
|
|
7
|
+
title: string;
|
|
8
|
+
nav: any[];
|
|
9
|
+
langs: any[];
|
|
10
|
+
currentLang: string;
|
|
11
|
+
github?: {
|
|
12
|
+
url: string;
|
|
13
|
+
title: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export const PageHeader = (props: PageHeaderProps) => {
|
|
17
|
+
const css: CssProps = {
|
|
18
|
+
width: '100%',
|
|
19
|
+
display: 'flex',
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
padding: '0.75rem 2rem',
|
|
22
|
+
justifyContent: 'space-between',
|
|
23
|
+
'.press-navbar-left': {
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
'.title': {
|
|
27
|
+
fontWeight: 'bold',
|
|
28
|
+
fontSize: '1.2rem',
|
|
29
|
+
marginRight: '1rem',
|
|
30
|
+
},
|
|
31
|
+
'.nav': {
|
|
32
|
+
display: 'flex',
|
|
33
|
+
gap: '1rem',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
'.press-navbar-right': {
|
|
37
|
+
display: 'flex',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
gap: '1.25rem',
|
|
40
|
+
'.navbar-item': {
|
|
41
|
+
display: 'flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
textDecoration: 'none',
|
|
44
|
+
transition: 'color 0.2s',
|
|
45
|
+
'&:hover': { color: 'var(--press-brand-color)' },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
return (
|
|
50
|
+
<header css={css} class='press-navbar'>
|
|
51
|
+
<div class='press-navbar-left'>
|
|
52
|
+
<div class='title'>
|
|
53
|
+
<a href='/'>{props.title}</a>
|
|
54
|
+
</div>
|
|
55
|
+
<nav class='nav'>
|
|
56
|
+
{props.nav.map((item: any) => (
|
|
57
|
+
<a href={item.link}>{item.text}</a>
|
|
58
|
+
))}
|
|
59
|
+
</nav>
|
|
60
|
+
</div>
|
|
61
|
+
<div class='press-navbar-right'>
|
|
62
|
+
{props.langs.length > 1 && (
|
|
63
|
+
<LangSwitcher className='navbar-item' currentLang={props.currentLang} langs={props.langs || []} />
|
|
64
|
+
)}
|
|
65
|
+
<ThemeSelector className='navbar-item' icon={<Svg>{themeIcon}</Svg>} noUpdateLabel={true} />
|
|
66
|
+
{props.github && props.github.url && (
|
|
67
|
+
<a
|
|
68
|
+
href={props.github.url}
|
|
69
|
+
target='_blank'
|
|
70
|
+
rel='noopener noreferrer'
|
|
71
|
+
class='navbar-item'
|
|
72
|
+
title={props.github.title}
|
|
73
|
+
>
|
|
74
|
+
<Svg>{githubIcon}</Svg>
|
|
75
|
+
</a>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
</header>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { CssProps } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export const PageHeading = (props: { headings: any[] }) => {
|
|
4
|
+
if (props.headings.length === 0) return null;
|
|
5
|
+
|
|
6
|
+
const css: CssProps = {
|
|
7
|
+
'&-title': {
|
|
8
|
+
fontWeight: 'bold',
|
|
9
|
+
fontSize: '0.8rem',
|
|
10
|
+
textTransform: 'uppercase',
|
|
11
|
+
letterSpacing: '0.05em',
|
|
12
|
+
color: 'var(--primary-color)',
|
|
13
|
+
marginBottom: '0.8rem',
|
|
14
|
+
},
|
|
15
|
+
'&-list': {
|
|
16
|
+
listStyle: 'none',
|
|
17
|
+
padding: 0,
|
|
18
|
+
margin: 0,
|
|
19
|
+
borderLeft: '1px solid var(--press-border-color)',
|
|
20
|
+
},
|
|
21
|
+
'&-item': {
|
|
22
|
+
padding: '0.2rem 0 0.2rem 1rem',
|
|
23
|
+
fontSize: '0.85rem',
|
|
24
|
+
'&.level-3': { paddingLeft: '2rem' },
|
|
25
|
+
a: {
|
|
26
|
+
display: 'block',
|
|
27
|
+
transition: 'color 0.2s',
|
|
28
|
+
whiteSpace: 'nowrap',
|
|
29
|
+
overflow: 'hidden',
|
|
30
|
+
textOverflow: 'ellipsis',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
return (
|
|
35
|
+
<div css={css}>
|
|
36
|
+
<div class='&-title'>On this page</div>
|
|
37
|
+
<ul class='&-list'>
|
|
38
|
+
{props.headings.map((h) => (
|
|
39
|
+
<li class={`&-item level-${h.level}`}>
|
|
40
|
+
<a href={`#${h.id}`}>{h.text}</a>
|
|
41
|
+
</li>
|
|
42
|
+
))}
|
|
43
|
+
</ul>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { CssProps } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export const LayoutHome = (props: { data: any }) => {
|
|
4
|
+
const { hero, features } = props.data || {};
|
|
5
|
+
const css: CssProps = {
|
|
6
|
+
'.&-hero': {
|
|
7
|
+
padding: '64px 32px',
|
|
8
|
+
textAlign: 'center',
|
|
9
|
+
display: 'flex',
|
|
10
|
+
flexDirection: 'column',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
},
|
|
13
|
+
'.&-hero-name': {
|
|
14
|
+
fontSize: '56px',
|
|
15
|
+
lineHeight: '64px',
|
|
16
|
+
fontWeight: 'bold',
|
|
17
|
+
background: 'linear-gradient(135deg, var(--press-brand-color) 30%, #4facfe 100%)',
|
|
18
|
+
'-webkit-background-clip': 'text',
|
|
19
|
+
'-webkit-text-fill-color': 'transparent',
|
|
20
|
+
},
|
|
21
|
+
'.&-hero-text': {
|
|
22
|
+
fontSize: '56px',
|
|
23
|
+
lineHeight: '64px',
|
|
24
|
+
fontWeight: 'bold',
|
|
25
|
+
marginTop: '8px',
|
|
26
|
+
},
|
|
27
|
+
'.&-hero-tagline': {
|
|
28
|
+
fontSize: '24px',
|
|
29
|
+
lineHeight: '36px',
|
|
30
|
+
color: 'var(--secondary-color)',
|
|
31
|
+
marginTop: '24px',
|
|
32
|
+
maxWidth: '576px',
|
|
33
|
+
},
|
|
34
|
+
'.&-hero-actions': {
|
|
35
|
+
display: 'flex',
|
|
36
|
+
gap: '12px',
|
|
37
|
+
marginTop: '48px',
|
|
38
|
+
},
|
|
39
|
+
'.&-button': {
|
|
40
|
+
display: 'inline-block',
|
|
41
|
+
padding: '0 20px',
|
|
42
|
+
lineHeight: '38px',
|
|
43
|
+
borderRadius: '20px',
|
|
44
|
+
fontWeight: '600',
|
|
45
|
+
textDecoration: 'none',
|
|
46
|
+
fontSize: '14px',
|
|
47
|
+
},
|
|
48
|
+
'.&-button.brand': {
|
|
49
|
+
backgroundColor: 'var(--press-brand-color)',
|
|
50
|
+
color: '#fff',
|
|
51
|
+
},
|
|
52
|
+
'.&-button.alt': {
|
|
53
|
+
backgroundColor: 'var(--secondary-bg-color)',
|
|
54
|
+
color: 'var(--primary-color)',
|
|
55
|
+
border: '1px solid var(--press-border-color)',
|
|
56
|
+
},
|
|
57
|
+
'.&-features': {
|
|
58
|
+
display: 'grid',
|
|
59
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(256px, 1fr))',
|
|
60
|
+
gap: '24px',
|
|
61
|
+
padding: '48px 32px',
|
|
62
|
+
maxWidth: '1152px',
|
|
63
|
+
margin: '0 auto',
|
|
64
|
+
},
|
|
65
|
+
'.&-feature-card': {
|
|
66
|
+
backgroundColor: 'var(--secondary-bg-color)',
|
|
67
|
+
padding: '24px',
|
|
68
|
+
borderRadius: '12px',
|
|
69
|
+
border: '1px solid var(--press-border-color)',
|
|
70
|
+
},
|
|
71
|
+
'.&-feature-title': {
|
|
72
|
+
fontSize: '20px',
|
|
73
|
+
fontWeight: 'bold',
|
|
74
|
+
marginBottom: '8px',
|
|
75
|
+
},
|
|
76
|
+
'.&-feature-details': {
|
|
77
|
+
fontSize: '14px',
|
|
78
|
+
color: 'var(--secondary-color)',
|
|
79
|
+
lineHeight: '22px',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div css={css}>
|
|
85
|
+
<section class='&-hero'>
|
|
86
|
+
<h1 class='&-hero-name'>{hero.name}</h1>
|
|
87
|
+
<p class='&-hero-text'>{hero.text}</p>
|
|
88
|
+
<p class='&-hero-tagline'>{hero.tagline}</p>
|
|
89
|
+
<div class='&-hero-actions'>
|
|
90
|
+
{hero.actions?.map((action: any) => (
|
|
91
|
+
<a href={action.link} class={`&-button ${action.theme}`}>
|
|
92
|
+
{action.text}
|
|
93
|
+
</a>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
</section>
|
|
97
|
+
{features && (
|
|
98
|
+
<section class='&-features'>
|
|
99
|
+
{features.map((feature: any) => (
|
|
100
|
+
<div class='&-feature-card'>
|
|
101
|
+
<h2 class='&-feature-title'>{feature.title}</h2>
|
|
102
|
+
<p class='&-feature-details'>{feature.details}</p>
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</section>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getCurrentLang } from 'lupine.web';
|
|
2
|
+
import { PressFrame } from '../frames/press-frame';
|
|
3
|
+
import { PageHeader } from './press-header';
|
|
4
|
+
import { PressSidemenu } from './press-sidemenu';
|
|
5
|
+
import { PressContent } from './press-content';
|
|
6
|
+
|
|
7
|
+
export const PressLayout = (props: {
|
|
8
|
+
children: any;
|
|
9
|
+
title: string;
|
|
10
|
+
nav: any[];
|
|
11
|
+
sidebar: any[];
|
|
12
|
+
lang: string;
|
|
13
|
+
langs?: any[];
|
|
14
|
+
data?: any;
|
|
15
|
+
headings?: any[];
|
|
16
|
+
sidemenuWidth?: string;
|
|
17
|
+
github?: { url: string; title: string };
|
|
18
|
+
}) => {
|
|
19
|
+
const isHome = props.data?.layout === 'home';
|
|
20
|
+
const headings = props.headings || [];
|
|
21
|
+
const currentLang = props.lang || getCurrentLang().langName;
|
|
22
|
+
|
|
23
|
+
const sidebar = props.sidebar || [];
|
|
24
|
+
const content = (
|
|
25
|
+
<PressContent sidebar={sidebar} isHome={isHome} headings={headings} data={props.data}>
|
|
26
|
+
{props.children}
|
|
27
|
+
</PressContent>
|
|
28
|
+
);
|
|
29
|
+
return (
|
|
30
|
+
<PressFrame
|
|
31
|
+
header={
|
|
32
|
+
<PageHeader
|
|
33
|
+
title={props.title}
|
|
34
|
+
nav={props.nav}
|
|
35
|
+
langs={props.langs || []}
|
|
36
|
+
currentLang={currentLang}
|
|
37
|
+
github={props.github}
|
|
38
|
+
/>
|
|
39
|
+
}
|
|
40
|
+
sidemenu={<PressSidemenu sidebar={sidebar} />}
|
|
41
|
+
content={content}
|
|
42
|
+
hideSidemenu={isHome}
|
|
43
|
+
sidemenuWidth={props.sidemenuWidth}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CssProps } from 'lupine.components';
|
|
2
|
+
import { pressLoad } from '../services/press-load';
|
|
3
|
+
|
|
4
|
+
export const PressSidemenu = (props: { sidebar: any[] }) => {
|
|
5
|
+
const css: CssProps = {
|
|
6
|
+
width: '100%',
|
|
7
|
+
padding: '0 8px 8px',
|
|
8
|
+
height: 'max-content',
|
|
9
|
+
// overflowY: 'auto',
|
|
10
|
+
'&-item': {
|
|
11
|
+
marginBottom: '0.3rem',
|
|
12
|
+
display: 'block',
|
|
13
|
+
color: 'var(--text-color)',
|
|
14
|
+
textDecoration: 'none',
|
|
15
|
+
'&:hover': {
|
|
16
|
+
color: 'var(--primary-color)',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
'&-group-title': {
|
|
20
|
+
fontWeight: 'bold',
|
|
21
|
+
marginTop: '0.5rem',
|
|
22
|
+
marginBottom: '0.5rem',
|
|
23
|
+
fontSize: '15px',
|
|
24
|
+
// color: 'var(--secondary-color)',
|
|
25
|
+
'&.group-level-0': {
|
|
26
|
+
marginTop: '1.5rem',
|
|
27
|
+
fontSize: '19px',
|
|
28
|
+
},
|
|
29
|
+
'&.group-level-1': {
|
|
30
|
+
marginTop: '0.75rem',
|
|
31
|
+
fontSize: '17px',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
'&-active': {
|
|
35
|
+
color: 'var(--primary-color)',
|
|
36
|
+
fontWeight: 'bold',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Expecting props.sidebar to be already flattened by parent
|
|
41
|
+
const flatList = props.sidebar || [];
|
|
42
|
+
const basePadding = 1; // rem
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<aside css={css}>
|
|
46
|
+
{flatList.map((item, index) => {
|
|
47
|
+
const style = { paddingLeft: `${item.level * basePadding}rem` };
|
|
48
|
+
|
|
49
|
+
if (item.type === 'group') {
|
|
50
|
+
return (
|
|
51
|
+
<div class={'&-group-title' + (' group-level-' + item.level)} style={style} key={index}>
|
|
52
|
+
{item.text}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
return (
|
|
57
|
+
<a
|
|
58
|
+
class='&-item'
|
|
59
|
+
style={style}
|
|
60
|
+
href='javascript:void(0)'
|
|
61
|
+
onClick={() => {
|
|
62
|
+
pressLoad(item.link);
|
|
63
|
+
return false;
|
|
64
|
+
}}
|
|
65
|
+
key={index}
|
|
66
|
+
>
|
|
67
|
+
{item.text}
|
|
68
|
+
</a>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
})}
|
|
72
|
+
</aside>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './press-frame';
|