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(syntaxHighlight);
|
||||||
config.addPlugin(markdown);
|
config.addPlugin(markdown);
|
||||||
|
|
||||||
|
config.addPassthroughCopy("src/assets");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
passthroughFileCopy: true,
|
passthroughFileCopy: true,
|
||||||
dataTemplateEngine: false,
|
dataTemplateEngine: false,
|
||||||
|
@ -10,4 +10,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/assets/js/react-examples.js" defer></script>
|
||||||
</body>
|
</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,5 +1,9 @@
|
|||||||
## 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
|
* 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
|
* But often browsers already give us the tools to do that natively
|
||||||
* Reaching for CSS and HTML first can make our components faster, more reusable, and more accessible
|
* Reaching for CSS and HTML first can make our components faster, more reusable, and more accessible
|
||||||
|
@ -147,6 +147,8 @@ export default class Toggle extends Component<Props, State> {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
|
<div data-react-root="toggle"></div>
|
||||||
|
|
||||||
* 139 lines of code (+ the Icons, theme, 3 libraries required)
|
* 139 lines of code (+ the Icons, theme, 3 libraries required)
|
||||||
* Manual state management
|
* Manual state management
|
||||||
* Have to import all of this everywhere we use it
|
* 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;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
overflow: hidden;
|
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 {
|
main {
|
||||||
@ -23,18 +43,11 @@ main {
|
|||||||
.slide {
|
.slide {
|
||||||
scroll-snap-align: start;
|
scroll-snap-align: start;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: 1px solid black;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 1 0 100%;
|
flex: 1 0 100%;
|
||||||
font-size: var(--text-size-m);
|
font-size: var(--text-size-m);
|
||||||
|
|
||||||
&.title {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-size-sm);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: var(--text-size-2xl);
|
font-size: var(--text-size-2xl);
|
||||||
@ -46,6 +59,25 @@ main {
|
|||||||
padding-inline: var(--space-size-xs);
|
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 {
|
h1, h2, h3, h4 {
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -83,9 +115,10 @@ main {
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: " ";
|
content: "";
|
||||||
width: 20px;
|
width: 20px;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
background: gray;
|
background: gray;
|
||||||
@ -113,7 +146,6 @@ main {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: space-around;
|
align-items: space-around;
|
||||||
gap: var(--space-size-m);
|
|
||||||
padding: var(--space-size-s);
|
padding: var(--space-size-s);
|
||||||
|
|
||||||
> :not(:first-child) {
|
> :not(:first-child) {
|
||||||
@ -123,6 +155,7 @@ main {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--space-size-s);
|
padding: var(--space-size-s);
|
||||||
|
flex-basis: 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> :first-child {
|
> :first-child {
|
||||||
@ -131,5 +164,58 @@ main {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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