First draft done
This commit is contained in:
parent
2fc934e324
commit
85bf38b5d5
@ -7,6 +7,8 @@ module.exports = function (config) {
|
||||
config.addPlugin(syntaxHighlight);
|
||||
config.addPlugin(markdown);
|
||||
|
||||
config.addPassthroughCopy("src/assets");
|
||||
|
||||
return {
|
||||
passthroughFileCopy: true,
|
||||
dataTemplateEngine: false,
|
||||
|
@ -10,4 +10,6 @@
|
||||
</head>
|
||||
<body>
|
||||
{{ content | safe }}
|
||||
|
||||
<script type="text/javascript" src="/assets/js/react-examples.js" defer></script>
|
||||
</body>
|
231
src/assets/js/react-examples.js
vendored
Normal file
231
src/assets/js/react-examples.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
src/assets/js/react-examples.js.map
Normal file
7
src/assets/js/react-examples.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +1,8 @@
|
||||
## What do you mean?
|
||||
---
|
||||
type: statement
|
||||
---
|
||||
|
||||
## Huh?
|
||||
|
||||
* When we create a custom component, often we have to tell the browser how it should treat it
|
||||
* But often browsers already give us the tools to do that natively
|
||||
|
@ -147,6 +147,8 @@ export default class Toggle extends Component<Props, State> {
|
||||
|
||||
<div>
|
||||
|
||||
<div data-react-root="toggle"></div>
|
||||
|
||||
* 139 lines of code (+ the Icons, theme, 3 libraries required)
|
||||
* Manual state management
|
||||
* Have to import all of this everywhere we use it
|
||||
|
191
src/slides/05-example-2.md
Normal file
191
src/slides/05-example-2.md
Normal file
@ -0,0 +1,191 @@
|
||||
## Example 2: Modals
|
||||
|
||||
<div class="grid">
|
||||
|
||||
```typescript
|
||||
import React, { MouseEventHandler, PureComponent, ReactNode } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { H4 } from '../Typography';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import isEventEscapeKey from 'helpers/isEventEscapeKey';
|
||||
import theme from 'theme';
|
||||
|
||||
export const CloseIcon = styled(Icon).attrs({
|
||||
size: 20,
|
||||
})`
|
||||
color: ${theme.colours.primary500};
|
||||
transition: color 0.3s ease;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 30px;
|
||||
z-index: 1;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ModalOverlay = styled.div`
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
`;
|
||||
|
||||
export const ModalContent = styled.div<StyleProps>`
|
||||
min-width: 500px;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
padding: 30px;
|
||||
position: relative;
|
||||
line-height: 1.43;
|
||||
max-height: 80%;
|
||||
|
||||
${({ scrollable }) =>
|
||||
scrollable
|
||||
? css`
|
||||
overflow-y: auto;
|
||||
`
|
||||
: ''};
|
||||
`;
|
||||
|
||||
export type StyleProps = {
|
||||
scrollable?: boolean;
|
||||
};
|
||||
|
||||
export type Props = StyleProps & {
|
||||
children: ReactNode;
|
||||
onClose: MouseEventHandler<HTMLDivElement | HTMLButtonElement>;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
class ModalContainer extends PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.onEscapePress);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.onEscapePress);
|
||||
}
|
||||
|
||||
overlayClicked: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
e.stopPropagation();
|
||||
if (!this.props.disabled && e.target === e.currentTarget) {
|
||||
this.props.onClose(e);
|
||||
}
|
||||
};
|
||||
|
||||
onEscapePress: EventListenerOrEventListenerObject = (e) => {
|
||||
e.stopPropagation();
|
||||
if (!this.props.disabled && isEventEscapeKey(e as KeyboardEvent)) {
|
||||
this.props.onClose(e as any);
|
||||
}
|
||||
};
|
||||
|
||||
onClose: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
e.stopPropagation();
|
||||
if (!this.props.disabled) {
|
||||
this.props.onClose(e);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const modal = (
|
||||
<ModalOverlay onClick={this.overlayClicked}>
|
||||
<ModalContent className={this.props.className} scrollable={this.props.scrollable}>
|
||||
<CloseIcon onClick={this.onClose} icon="close" />
|
||||
{this.props.children}
|
||||
</ModalContent>
|
||||
</ModalOverlay>
|
||||
);
|
||||
|
||||
// defaulting to document.body mostly for the benefit of tests, which don't have a #app-container element.
|
||||
const rootElement = document.getElementById('app-container') || document.body;
|
||||
|
||||
return ReactDOM.createPortal(modal, rootElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const ModalContents = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const ModalHeader = styled(H4)`
|
||||
margin-bottom: 15px;
|
||||
`;
|
||||
|
||||
export const ModalBody = styled.div`
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
`;
|
||||
|
||||
export const ModalFooter = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
|
||||
& > {
|
||||
margin-left: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
header: ReactNode;
|
||||
body: ReactNode;
|
||||
footer?: ReactNode;
|
||||
loading?: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
class Modal extends PureComponent<Props> {
|
||||
render() {
|
||||
const { visible, header, body, footer, onClose, loading } = this.props;
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalContainer onClose={onClose} disabled={loading} scrollable={true}>
|
||||
<ModalContents>
|
||||
<ModalHeader>{header}</ModalHeader>
|
||||
<ModalBody>{body}</ModalBody>
|
||||
<ModalFooter>{footer}</ModalFooter>
|
||||
</ModalContents>
|
||||
</ModalContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div>
|
||||
|
||||
<div data-react-root="modal"></div>
|
||||
|
||||
* 171 lines of code
|
||||
* Requires state updates to display
|
||||
* Separate component just to manage the backdrop
|
||||
* How does a screen reader announce that the modal is displayed? (hint: it doesn't)
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
72
src/slides/06-example-2.html
Normal file
72
src/slides/06-example-2.html
Normal file
@ -0,0 +1,72 @@
|
||||
<h2>Example 2: Modals are just Dialogs</h2>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
{% highlight "html" %}
|
||||
<style>
|
||||
dialog {
|
||||
z-index: 10;
|
||||
}
|
||||
dialog::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
<dialog id="displayDialog">
|
||||
<form method="dialog">
|
||||
<button autofocus>Close</button>
|
||||
</form>
|
||||
|
||||
<h1>I'm a dialog!</h1>
|
||||
</dialog>
|
||||
|
||||
<button onclick="showModal()">Open the dialog as a modal</button>
|
||||
<button onclick="showDialog()">Open the dialog as a popup</button>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
const dialog = document.getElementById("displayDialog");
|
||||
|
||||
function showModal() {
|
||||
dialog.showModal();
|
||||
}
|
||||
|
||||
function showDialog() {
|
||||
dialog.show();
|
||||
}
|
||||
</script>
|
||||
{% endhighlight %}
|
||||
|
||||
<div>
|
||||
<dialog id="displayDialog">
|
||||
<form method="dialog">
|
||||
<button autofocus>Close</button>
|
||||
</form>
|
||||
|
||||
<h1>I'm a dialog!</h1>
|
||||
</dialog>
|
||||
|
||||
<button onclick="showModal()">Open the dialog as a modal</button>
|
||||
<button onclick="showDialog()">Open the dialog as a popup</button>
|
||||
|
||||
<ul>
|
||||
<li>31 lines of code</li>
|
||||
<li>A teensy bit of Javascript - I know, I'm a hypocrite</li>
|
||||
<li>Option to use as a popup <i>or</i> a modal</li>
|
||||
<li><code>::backdrop</code> pseudo-element for easy styling</li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">
|
||||
const dialog = document.getElementById("displayDialog");
|
||||
|
||||
function showModal() {
|
||||
dialog.showModal();
|
||||
}
|
||||
|
||||
function showDialog() {
|
||||
dialog.show();
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</div>
|
86
src/slides/07-example-3.md
Normal file
86
src/slides/07-example-3.md
Normal file
@ -0,0 +1,86 @@
|
||||
## Example 3: Accordions
|
||||
|
||||
<div class="grid">
|
||||
|
||||
```typescript
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Icon from '../../../../../components/Icon';
|
||||
import theme from '../../../../../theme';
|
||||
|
||||
type AccordionProps = {
|
||||
title: string;
|
||||
items: (string | null)[];
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const AccordionSummary = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
interface ExpandedProps {
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export const StyledArrow = styled(Icon).attrs({
|
||||
icon: 'dropdownArrow',
|
||||
size: 15,
|
||||
color: theme.colours.primary500,
|
||||
})<ExpandedProps>`
|
||||
position: relative;
|
||||
transform: rotate(${({ expanded }) => (expanded ? '180deg' : '0')});
|
||||
`;
|
||||
|
||||
const Title = styled.span<ExpandedProps>`
|
||||
font-weight: ${({ expanded }) => (expanded ? 'bold' : 'normal')};
|
||||
`;
|
||||
|
||||
const ItemList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const Accordion = ({ title, items }: AccordionProps) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AccordionSummary
|
||||
onClick={() => {
|
||||
setExpanded(!expanded);
|
||||
}}
|
||||
>
|
||||
<Title expanded={expanded}>{title}</Title>
|
||||
<StyledArrow expanded={expanded} />
|
||||
</AccordionSummary>
|
||||
{expanded && (
|
||||
<ItemList>
|
||||
{items.map((item) => {
|
||||
return <div key={item}>{item}</div>;
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Accordion;
|
||||
```
|
||||
|
||||
<div>
|
||||
|
||||
<div data-react-root="accordion"></div>
|
||||
|
||||
* 67 lines of code
|
||||
* Manual state management
|
||||
* Divs with onClick handlers - unreachable by keyboard, no screen reader support
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
91
src/slides/08-example-3.md
Normal file
91
src/slides/08-example-3.md
Normal file
@ -0,0 +1,91 @@
|
||||
## Example 3: The discloure element
|
||||
|
||||
|
||||
<div class="grid">
|
||||
|
||||
```html
|
||||
<style>
|
||||
details {
|
||||
position: relative;
|
||||
width: max-content;
|
||||
min-width: 20ch;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
border: 1px solid darkgray;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-bottom: 1px solid transparent;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
summary svg {
|
||||
width: 12px;
|
||||
height: auto;
|
||||
transform: rotate(180deg) translate(-1px);
|
||||
transition: transform .3s linear;
|
||||
}
|
||||
|
||||
summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
details[open] > summary {
|
||||
border-color: darkgray;
|
||||
}
|
||||
|
||||
details[open] summary svg {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
<details>
|
||||
<summary>Test Title <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 7">
|
||||
<path
|
||||
fill="none"
|
||||
fillRule="evenodd"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
d="M1 1l4.025 4L9 1.05"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</summary>
|
||||
|
||||
<ul>
|
||||
<li>Test item 1</li>
|
||||
<li>Test item 2</li>
|
||||
</ul>
|
||||
</details>
|
||||
```
|
||||
|
||||
<div>
|
||||
|
||||
<details>
|
||||
<summary>Test Title <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 7">
|
||||
<path
|
||||
fill="none"
|
||||
fillRule="evenodd"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
d="M1 1l4.025 4L9 1.05"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</summary>
|
||||
|
||||
<ul>
|
||||
<li>Test item 1</li>
|
||||
<li>Test item 2</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
12
src/slides/09-final-points.md
Normal file
12
src/slides/09-final-points.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
type: statement
|
||||
---
|
||||
|
||||
## Final points
|
||||
|
||||
* The minified, optimized React bundle I produced to showcase just _3_ components was 190kb before Gzipping
|
||||
* The majority of that is React - 143kb
|
||||
* This entire presentation, with lots of duplication, and every single slide included, is only 92kb
|
||||
* When we build new components, let's see if the browser can't do it for us instead
|
||||
* Modern CSS is great
|
||||
* Javascript as a garnish, not a main course
|
9
src/slides/10-useful-links.md
Normal file
9
src/slides/10-useful-links.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
type: statement
|
||||
---
|
||||
|
||||
## Useful links
|
||||
|
||||
* W3C Aria Patterns - https://www.w3.org/WAI/ARIA/apg/patterns/
|
||||
* Every Layout - https://every-layout.dev/
|
||||
* Web.dev - https://web.dev/
|
@ -7,6 +7,26 @@ body {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
overflow: hidden;
|
||||
font-family: Avenir, Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: .5rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--color-slate-800);
|
||||
outline: 1px solid var(--color-slate-800);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
main {
|
||||
@ -23,18 +43,11 @@ main {
|
||||
.slide {
|
||||
scroll-snap-align: start;
|
||||
position: relative;
|
||||
border-bottom: 1px solid black;
|
||||
height: 100%;
|
||||
flex: 1 0 100%;
|
||||
font-size: var(--text-size-m);
|
||||
|
||||
&.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-size-sm);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: var(--text-size-2xl);
|
||||
@ -46,6 +59,25 @@ main {
|
||||
padding-inline: var(--space-size-xs);
|
||||
}
|
||||
|
||||
&.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-size-sm);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
h2 {
|
||||
border-bottom: 0px solid;
|
||||
}
|
||||
}
|
||||
|
||||
&.statement {
|
||||
ul {
|
||||
width: 100%;
|
||||
margin: var(--space-size-3xl) auto;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
a {
|
||||
text-decoration: none;
|
||||
@ -84,8 +116,9 @@ main {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
&::before {
|
||||
content: " ";
|
||||
content: "";
|
||||
width: 20px;
|
||||
aspect-ratio: 1;
|
||||
background: gray;
|
||||
@ -113,7 +146,6 @@ main {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: space-around;
|
||||
gap: var(--space-size-m);
|
||||
padding: var(--space-size-s);
|
||||
|
||||
> :not(:first-child) {
|
||||
@ -123,6 +155,7 @@ main {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--space-size-s);
|
||||
flex-basis: 40%;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
@ -132,4 +165,57 @@ main {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dialog {
|
||||
z-index: 10;
|
||||
|
||||
&::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin-block-start: var(--space-size-s);
|
||||
}
|
||||
|
||||
details {
|
||||
position: relative;
|
||||
width: max-content;
|
||||
min-width: 20ch;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
border: 1px solid darkgray;
|
||||
color: black;
|
||||
|
||||
summary {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-bottom: 1px solid transparent;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
svg {
|
||||
width: var(--text-size-s);
|
||||
height: auto;
|
||||
transform: rotate(180deg) translate(-1px);
|
||||
transition: transform .3s linear;
|
||||
}
|
||||
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[open] summary {
|
||||
border-color: darkgray;
|
||||
|
||||
svg {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user