Skip to content

Commit 8e75343

Browse files
Merge pull request #5275 from material-components:stories-page
PiperOrigin-RevId: 590302109
2 parents 27d3541 + 4f4792a commit 8e75343

File tree

8 files changed

+402
-28
lines changed

8 files changed

+402
-28
lines changed

catalog/eleventy-helpers/shortcodes/playground-example.cjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function playgroundExample(eleventyConfig) {
5151
<md-icon aria-hidden="true">expand_more</md-icon>
5252
<md-icon aria-hidden="true" slot="selected">expand_less</md-icon>
5353
</md-outlined-icon-button>
54-
Expand interactive demo.
54+
View interactive demo inline.
5555
</summary>
5656
<lit-island on:visible import="/js/hydration-entrypoints/playground-elements.js" class="example" aria-hidden="true">
5757
<playground-project
@@ -68,6 +68,7 @@ function playgroundExample(eleventyConfig) {
6868
><md-circular-progress indeterminate></md-circular-progress></playground-file-editor>
6969
</lit-island>
7070
</details>
71+
<p><a href="./stories/" target="_blank">Open interactive demo in new tab.</a></p>
7172
`;
7273
});
7374
}

catalog/site/css/stories.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#dragbar {
8+
max-width: 100%;
9+
max-height: 100%;
10+
}
11+
12+
#editor {
13+
margin-block: 0;
14+
height: 100%;
15+
box-sizing: border-box;
16+
}
17+
18+
#editor-wrapper {
19+
height: 100%;
20+
overflow: hidden;
21+
}
22+
23+
body {
24+
height: 100dvh;
25+
}
26+
27+
#preview {
28+
position: relative;
29+
}
30+
31+
#preview md-circular-progress {
32+
inset: 50%;
33+
transform: translate(-50%, -50%);
34+
}

catalog/site/css/syntax-highlight.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
/* Formats the code boxes themselves */
5252
.example playground-file-editor,
53+
playground-file-editor,
5354
pre[class*='language-'] {
5455
padding: var(--__code-block-font-size);
5556
/* Remove the extra hard coded 3px from line number padding. */

catalog/site/stories/stories.html

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---js
2+
{
3+
pagination: {
4+
data: "collections.component",
5+
size: 1,
6+
alias: "component",
7+
before: components => {
8+
// remove any components that don't have a dirname
9+
return components.filter(component => component.data.dirname)
10+
}
11+
},
12+
permalink: "components/{{component.data.page.fileSlug}}/stories/index.html",
13+
fullHeightContent: "true",
14+
collections: ["stories"],
15+
eleventyComputed: {
16+
dirname: ({component}) => component.data.dirname,
17+
}
18+
}
19+
---
20+
21+
<!DOCTYPE html>
22+
<html lang="en">
23+
<head>
24+
<meta charset="utf-8" />
25+
<meta
26+
name="viewport"
27+
content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"
28+
/>
29+
<title>Material Web - Stories {{component.data.name}}</title>
30+
<!-- Set the color of the url bar on mobile to match theme -->
31+
<meta name="theme-color" content="#251f16" />
32+
<link
33+
href="/images/favicon.svg"
34+
rel="icon"
35+
sizes="any"
36+
type="image/svg+xml"
37+
/>
38+
<!-- Inlines the global css in site/css/global.css -->
39+
{% inlinecss "global.css" %}
40+
<!-- MUST be loaded before any lit bundle. allows hydration of SSRd components -->
41+
<script type="module" src="/js/ssr-utils/lit-hydrate-support.js"></script>
42+
<!-- Inlines the material theming logic since we want to prevent FOUC -->
43+
{% inlinejs "inline/apply-saved-theme.js" %}
44+
<!-- Needed for intializing theme if this is the first page they ever visit -->
45+
<script type="module" src="/js/pages/global.js"></script>
46+
<noscript>
47+
<link rel="stylesheet" href="/css/no-js.css" />
48+
</noscript>
49+
<link
50+
rel="stylesheet"
51+
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&display=swap"
52+
/>
53+
<!-- If JS is disabled just show the contents without the polyfill -->
54+
<noscript
55+
><style>
56+
body[dsd-pending] {
57+
display: block !important;
58+
}
59+
</style>
60+
</noscript>
61+
<!-- Syncs theme with playground -->
62+
<script type="module" src="/js/pages/components.js"></script>
63+
<script type="module" src="/js/pages/stories.js"></script>
64+
<script
65+
type="module"
66+
src="/js/hydration-entrypoints/playground-elements.js"
67+
></script>
68+
<link rel="stylesheet" href="/css/syntax-highlight.css" />
69+
<link rel="stylesheet" href="/css/stories.css" />
70+
</head>
71+
<!-- dsd-pending hides body until the polyfill has run on browsers that do not support DSD -->
72+
<body dsd-pending>
73+
<!-- Inlines the declarative shadow dom polyfill for FF since it's performance sensitive -->
74+
{% inlinejs "ssr-utils/dsd-polyfill.js" %}
75+
<playground-project
76+
id="project"
77+
project-src="/assets/stories/{{dirname}}/project.json"
78+
>
79+
</playground-project>
80+
<drag-playground id="dragbar">
81+
<playground-preview id="preview" project="project" slot="preview">
82+
<md-circular-progress indeterminate></md-circular-progress>
83+
</playground-preview>
84+
<div slot="editor" id="editor-wrapper">
85+
<playground-file-editor
86+
id="editor"
87+
project="project"
88+
filename="stories.ts"
89+
line-numbers
90+
aria-hidden="true"
91+
>
92+
<md-circular-progress indeterminate></md-circular-progress>
93+
</playground-file-editor>
94+
</div>
95+
</drag-playground>
96+
</body>
97+
</html>
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
8+
import { LitElement, css, html } from 'lit';
9+
import { customElement, state, query } from 'lit/decorators.js';
10+
import { classMap } from 'lit/directives/class-map.js';
11+
import { styleMap } from 'lit/directives/style-map.js';
12+
import '@material/web/icon/icon.js';
13+
14+
/**
15+
* A playground preview + editor with a draggable handle.
16+
*/
17+
@customElement('drag-playground')
18+
export class DragPlayground extends LitElement {
19+
static styles = css`
20+
:host {
21+
display: block;
22+
--_drag-bar-height: 24px;
23+
--_drag-bar-border-width: 1px;
24+
--_half-drag-bar-height: calc(
25+
(var(--_drag-bar-height) / 2) + var(--_drag-bar-border-width)
26+
);
27+
}
28+
29+
#wrapper {
30+
display: flex;
31+
flex-direction: column;
32+
}
33+
34+
:host,
35+
#wrapper,
36+
::slotted(*) {
37+
height: 100%;
38+
}
39+
40+
slot {
41+
display: block;
42+
overflow: hidden;
43+
}
44+
45+
[name='preview'] {
46+
height: max(
47+
calc(
48+
100% - var(--editor-percentage, 0%) - var(--_half-drag-bar-height)
49+
),
50+
0px
51+
);
52+
}
53+
54+
[name='editor'] {
55+
height: max(
56+
calc(var(--editor-percentage, 0px) - var(--_half-drag-bar-height)),
57+
0px
58+
);
59+
}
60+
61+
#drag-bar {
62+
touch-action: none;
63+
background-color: var(--md-sys-color-surface-container);
64+
color: var(--md-sys-color-on-surface);
65+
border: var(--_drag-bar-border-width) solid var(--md-sys-color-outline);
66+
border-radius: 12px;
67+
height: var(--_drag-bar-height);
68+
display: flex;
69+
justify-content: center;
70+
align-items: center;
71+
-webkit-user-select: none;
72+
user-select: none;
73+
}
74+
75+
#drag-bar:hover {
76+
background-color: var(--md-sys-color-surface-container-high);
77+
cursor: grab;
78+
}
79+
80+
#drag-bar.isDragging {
81+
background-color: var(--md-sys-color-inverse-surface);
82+
color: var(--md-sys-color-inverse-on-surface);
83+
cursor: grabbing;
84+
}
85+
`;
86+
87+
/**
88+
* Whether or not we are in the "dragging" state.
89+
*/
90+
@state() private isDragging = false;
91+
92+
/**
93+
* The percentage of the editor height.
94+
*/
95+
@state() private editorHeightPercent = 0;
96+
97+
@query('#wrapper') private wrapperEl!: HTMLElement;
98+
99+
/**
100+
* A set of pointer IDs in the case that the user is dragging with multiple
101+
* pointers.
102+
*/
103+
private pointerIds = new Set<number>();
104+
105+
render() {
106+
return html`<div
107+
id="wrapper"
108+
style=${styleMap({
109+
'--editor-percentage': `${this.editorHeightPercent}%`,
110+
})}
111+
>
112+
<slot name="preview"></slot>
113+
<div
114+
id="drag-bar"
115+
tabindex="0"
116+
role="slider"
117+
aria-orientation="vertical"
118+
aria-valuemax="100"
119+
aria-valuemin="0"
120+
aria-valuenow="${this.editorHeightPercent}"
121+
aria-valuetext="${this.editorHeightPercent} percent"
122+
aria-label="Editor height"
123+
@focus=${this.onFocus}
124+
@blur=${this.onBlur}
125+
@keydown=${this.onKeydown}
126+
@pointerdown=${this.onPointerdown}
127+
@pointerup=${this.onPointerup}
128+
@pointermove=${this.onPointermove}
129+
class=${classMap({
130+
isDragging: this.isDragging,
131+
})}
132+
>
133+
<md-icon>drag_handle</md-icon>
134+
</div>
135+
<slot name="editor"></slot>
136+
</div>`;
137+
}
138+
139+
private onFocus() {
140+
this.isDragging = true;
141+
}
142+
143+
private onBlur() {
144+
this.isDragging = false;
145+
}
146+
147+
private onKeydown(event: KeyboardEvent) {
148+
const { key } = event;
149+
switch (key) {
150+
case 'ArrowRight':
151+
case 'ArrowUp':
152+
this.editorHeightPercent = Math.min(this.editorHeightPercent + 1, 100);
153+
break;
154+
case 'ArrowLeft':
155+
case 'ArrowDown':
156+
this.editorHeightPercent = Math.max(this.editorHeightPercent - 1, 0);
157+
break;
158+
case 'PageUp':
159+
this.editorHeightPercent = Math.min(this.editorHeightPercent + 10, 100);
160+
break;
161+
case 'PageDown':
162+
this.editorHeightPercent = Math.max(this.editorHeightPercent - 10, 0);
163+
break;
164+
case 'Home':
165+
this.editorHeightPercent = 0;
166+
break;
167+
case 'End':
168+
this.editorHeightPercent = 100;
169+
break;
170+
default:
171+
break;
172+
}
173+
}
174+
175+
private onPointerdown(event: PointerEvent) {
176+
this.isDragging = true;
177+
178+
if (this.pointerIds.has(event.pointerId)) return;
179+
180+
this.pointerIds.add(event.pointerId);
181+
(event.target as HTMLElement).setPointerCapture(event.pointerId);
182+
}
183+
184+
private onPointerup(event: PointerEvent) {
185+
this.pointerIds.delete(event.pointerId);
186+
(event.target as HTMLElement).releasePointerCapture(event.pointerId);
187+
188+
if (this.pointerIds.size === 0) {
189+
this.isDragging = false;
190+
}
191+
}
192+
193+
private onPointermove(event: PointerEvent) {
194+
if (!this.isDragging) return;
195+
196+
const { clientY: mouseY } = event;
197+
const { top: wrapperTop, bottom: wrapperBottom } =
198+
this.wrapperEl.getBoundingClientRect();
199+
200+
// The height of the wrapper
201+
const height = wrapperBottom - wrapperTop;
202+
203+
// Calculate the percentage of the editor height in which the pointer is
204+
// located
205+
const editorHeightPercent = 100 - ((mouseY - wrapperTop) / height) * 100;
206+
207+
// Clamp the percentage between 0 and 100
208+
this.editorHeightPercent = Math.min(Math.max(editorHeightPercent, 0), 100);
209+
}
210+
}
211+
212+
declare global {
213+
interface HTMLElementTagNameMap {
214+
'drag-playground': DragPlayground;
215+
}
216+
}

catalog/src/pages/stories.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import '../components/drag-playground.js';

catalog/src/ssr.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ import './components/catalog-component-header-title.js';
1212
import './components/nav-drawer.js';
1313
import './components/theme-changer.js';
1414
import './components/top-app-bar.js';
15+
import './components/drag-playground.js';
1516
// 🤫
1617
import '@material/web/labs/item/item.js';

0 commit comments

Comments
 (0)