graphen 2.0.3 → 2.0.5

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": "graphen",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "Graphen is a small library, that keeps reusable blocks of UI and helps making application design consistent across multiple projects.",
5
5
  "main": "src/index.ts",
6
6
  "files": [
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+ import { IconImg } from "../Icons";
4
+
5
+ type Props = {
6
+ className?: string;
7
+ };
8
+
9
+ function CoverEmpty({ className = "" }: Props) {
10
+ const classes = classNames("gc-cover-empty", className);
11
+
12
+ return (
13
+ <span className={classes}>
14
+ <IconImg className="gc-cover-empty__icon" />
15
+ </span>
16
+ );
17
+ }
18
+
19
+ export default CoverEmpty;
@@ -0,0 +1,17 @@
1
+ .gc-cover-empty {
2
+ display: grid;
3
+ place-items: center;
4
+ width: 100%;
5
+ height: 100%;
6
+ color: $color-silver;
7
+
8
+ &__icon {
9
+ width: 38px;
10
+ height: 38px;
11
+ stroke: currentcolor;
12
+ fill: none;
13
+ stroke-width: 1.4;
14
+ stroke-linecap: round;
15
+ stroke-linejoin: round;
16
+ }
17
+ }
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+
3
+ type IconProps = {
4
+ className?: string;
5
+ };
6
+
7
+ export const IconSearch = ({ className }: IconProps) => (
8
+ <svg className={className} viewBox="0 0 24 24">
9
+ <circle cx="11" cy="11" r="7" />
10
+ <path d="M21 21l-4.3-4.3" />
11
+ </svg>
12
+ );
13
+
14
+ export const IconEdit = ({ className }: IconProps) => (
15
+ <svg className={className} viewBox="0 0 24 24">
16
+ <path d="M12 20h9" />
17
+ <path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z" />
18
+ </svg>
19
+ );
20
+
21
+ export const IconTrash = ({ className }: IconProps) => (
22
+ <svg className={className} viewBox="0 0 24 24">
23
+ <path d="M3 6h18" />
24
+ <path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2" />
25
+ <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
26
+ </svg>
27
+ );
28
+
29
+ export const IconPlus = ({ className }: IconProps) => (
30
+ <svg className={className} viewBox="0 0 24 24">
31
+ <path d="M12 5v14M5 12h14" />
32
+ </svg>
33
+ );
34
+
35
+ export const IconRefresh = ({ className }: IconProps) => (
36
+ <svg className={className} viewBox="0 0 24 24">
37
+ <path d="M21 12a9 9 0 1 1-2.64-6.36" />
38
+ <path d="M21 3v6h-6" />
39
+ </svg>
40
+ );
41
+
42
+ export const IconClock = ({ className }: IconProps) => (
43
+ <svg className={className} viewBox="0 0 24 24">
44
+ <circle cx="12" cy="12" r="9" />
45
+ <path d="M12 7v5l3 2" />
46
+ </svg>
47
+ );
48
+
49
+ export const IconImg = ({ className }: IconProps) => (
50
+ <svg className={className} viewBox="0 0 24 24">
51
+ <rect x="3" y="3" width="18" height="18" rx="2" />
52
+ <circle cx="8.5" cy="8.5" r="1.5" />
53
+ <path d="M21 15l-5-5L5 21" />
54
+ </svg>
55
+ );
56
+
57
+ export const IconMail = ({ className }: IconProps) => (
58
+ <svg className={className} viewBox="0 0 24 24">
59
+ <rect x="3" y="5" width="18" height="14" rx="2" />
60
+ <path d="M3 7l9 6 9-6" />
61
+ </svg>
62
+ );
63
+
64
+ export const IconCheck = ({ className }: IconProps) => (
65
+ <svg className={className} viewBox="0 0 24 24">
66
+ <path d="M20 6L9 17l-5-5" />
67
+ </svg>
68
+ );
69
+
70
+ export const IconChevron = ({ className }: IconProps) => (
71
+ <svg className={className} viewBox="0 0 24 24">
72
+ <path d="M6 9l6 6 6-6" />
73
+ </svg>
74
+ );
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+
4
+ export type SegmentedControlOption<TValue extends string = string> = {
5
+ value: TValue;
6
+ label: React.ReactNode;
7
+ };
8
+
9
+ type Props<TValue extends string = string> = {
10
+ options: ReadonlyArray<SegmentedControlOption<TValue>>;
11
+ value: TValue;
12
+ onChange: (value: TValue) => void;
13
+ className?: string;
14
+ };
15
+
16
+ function SegmentedControl<TValue extends string = string>({
17
+ options,
18
+ value,
19
+ onChange,
20
+ className = "",
21
+ }: Props<TValue>) {
22
+ return (
23
+ <div
24
+ className={classNames("gc-segmented-control", className)}
25
+ role="tablist"
26
+ >
27
+ {options.map((option) => (
28
+ <button
29
+ key={option.value}
30
+ type="button"
31
+ role="tab"
32
+ aria-selected={value === option.value}
33
+ className={classNames("gc-segmented-control__option", {
34
+ "gc-segmented-control__option--on": value === option.value,
35
+ })}
36
+ onClick={() => onChange(option.value)}
37
+ >
38
+ {option.label}
39
+ </button>
40
+ ))}
41
+ </div>
42
+ );
43
+ }
44
+
45
+ export default SegmentedControl;
@@ -0,0 +1,26 @@
1
+ .gc-segmented-control {
2
+ display: inline-flex;
3
+ background: $gb-color-component;
4
+ border: 1px solid $gb-color-component-dark;
5
+ border-radius: 999px;
6
+ padding: 3px;
7
+
8
+ &__option {
9
+ border: 0;
10
+ background: none;
11
+ padding: 7px 16px;
12
+ border-radius: 999px;
13
+ font-size: 13.5px;
14
+ font-weight: 600;
15
+ color: $color-boulder;
16
+ cursor: pointer;
17
+
18
+ &--on {
19
+ background: $color-white;
20
+ color: $gb-color-primary;
21
+ box-shadow:
22
+ 0 1px 2px rgb(33 43 54 / 5%),
23
+ 0 1px 1px rgb(33 43 54 / 3%);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+
4
+ export type StatModifier = "gray" | "blue" | "green" | "amber" | "red";
5
+
6
+ type Props = {
7
+ modifier?: StatModifier;
8
+ label: React.ReactNode;
9
+ value: React.ReactNode;
10
+ className?: string;
11
+ };
12
+
13
+ function Stat({ modifier = "gray", label, value, className = "" }: Props) {
14
+ return (
15
+ <div className={classNames("gc-stat", `gc-stat--${modifier}`, className)}>
16
+ <div className="gc-stat__key">
17
+ <span className="gc-stat__dot" />
18
+ {label}
19
+ </div>
20
+ <div className="gc-stat__value">{value}</div>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ export default Stat;
@@ -0,0 +1,51 @@
1
+ .gc-stat {
2
+ background: $color-white;
3
+ border: 1px solid $gb-color-component-dark;
4
+ border-radius: $radius-large;
5
+ padding: 16px 18px;
6
+ box-shadow:
7
+ 0 1px 2px rgb(33 43 54 / 5%),
8
+ 0 1px 1px rgb(33 43 54 / 3%);
9
+
10
+ &__key {
11
+ font-family: $gb-font-mono;
12
+ font-size: 11px;
13
+ text-transform: uppercase;
14
+ letter-spacing: 0.04em;
15
+ color: $color-boulder;
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 7px;
19
+ }
20
+
21
+ &__dot {
22
+ width: 7px;
23
+ height: 7px;
24
+ border-radius: 50%;
25
+ background: $color-silver;
26
+ }
27
+
28
+ &__value {
29
+ font-size: 27px;
30
+ font-weight: 600;
31
+ color: $color-mine-shaft;
32
+ margin-top: 8px;
33
+ letter-spacing: -0.02em;
34
+ }
35
+
36
+ &--blue &__dot {
37
+ background: $gb-color-primary;
38
+ }
39
+
40
+ &--green &__dot {
41
+ background: $gb-color-success;
42
+ }
43
+
44
+ &--amber &__dot {
45
+ background: #c77b12;
46
+ }
47
+
48
+ &--red &__dot {
49
+ background: $gb-color-danger;
50
+ }
51
+ }
@@ -573,6 +573,38 @@ body.docs-body {
573
573
  svg { width: 18px; height: 18px; color: var(--text); }
574
574
  }
575
575
 
576
+ .docs-iconset-cell__svg {
577
+ width: 18px;
578
+ height: 18px;
579
+ fill: none;
580
+ stroke: currentcolor;
581
+ stroke-width: 1.7;
582
+ stroke-linecap: round;
583
+ stroke-linejoin: round;
584
+ }
585
+
586
+ .docs-subsection-title {
587
+ margin: 28px 0 6px;
588
+ font-size: 15px;
589
+ font-weight: 600;
590
+ color: var(--text);
591
+ }
592
+
593
+ .docs-stat-grid {
594
+ display: grid;
595
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
596
+ gap: 12px;
597
+ width: 100%;
598
+ }
599
+
600
+ .docs-cover-empty-stage {
601
+ width: 160px;
602
+ height: 120px;
603
+ border: 1px solid var(--border);
604
+ border-radius: var(--radius-lg);
605
+ overflow: hidden;
606
+ }
607
+
576
608
  .docs-type-row {
577
609
  display: grid;
578
610
  grid-template-columns: 80px 80px 1fr;
@@ -613,6 +645,72 @@ body.docs-body {
613
645
  .swatch-radius { width: 36px; height: 24px; background: var(--surface-2); border: 1px solid var(--border-strong); }
614
646
  }
615
647
 
648
+ .docs-bp-grid {
649
+ background: var(--bg-elev);
650
+ border: 1px solid var(--border);
651
+ border-radius: var(--radius-lg);
652
+ overflow: hidden;
653
+ }
654
+
655
+ .docs-bp-row {
656
+ display: grid;
657
+ grid-template-columns: 180px 120px 1fr;
658
+ gap: 16px;
659
+ align-items: center;
660
+ padding: 10px 18px;
661
+ border-top: 1px solid var(--border);
662
+
663
+ &:first-child { border-top: none; }
664
+
665
+ .mixin,
666
+ .range { font-family: var(--font-mono); font-size: 12.5px; }
667
+ .range { color: var(--text-muted); }
668
+ .vars { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
669
+ }
670
+
671
+ .docs-bp-demo {
672
+ display: flex;
673
+ flex-wrap: wrap;
674
+ justify-content: center;
675
+ gap: 12px;
676
+
677
+ .pill {
678
+ display: flex;
679
+ flex-direction: column;
680
+ align-items: center;
681
+ gap: 2px;
682
+ min-width: 128px;
683
+ padding: 10px 18px;
684
+ border: 1px dashed var(--border-strong);
685
+ border-radius: var(--radius);
686
+ opacity: 0.35;
687
+ transition: opacity 0.15s, border-color 0.15s, background 0.15s;
688
+
689
+ .nm { font-family: var(--font-mono); font-size: 12.5px; color: var(--text); }
690
+ .rg { font-family: var(--font-mono); font-size: 11px; color: var(--text-subtle); }
691
+ }
692
+ }
693
+
694
+ /* The live part of the Responsiveness demo — powered by the
695
+ library's own gx-* mixins, not by docs-local media queries. */
696
+ @mixin docs-bp-pill-active {
697
+ background: var(--accent-soft);
698
+ border: 1px solid var(--accent);
699
+ opacity: 1;
700
+ }
701
+
702
+ @include gx-phone {
703
+ .docs-bp-demo .pill--phone { @include docs-bp-pill-active; }
704
+ }
705
+
706
+ @include gx-tablet {
707
+ .docs-bp-demo .pill--tablet { @include docs-bp-pill-active; }
708
+ }
709
+
710
+ @include gx-desktop {
711
+ .docs-bp-demo .pill--desktop { @include docs-bp-pill-active; }
712
+ }
713
+
616
714
  .docs-comp-index {
617
715
  display: grid;
618
716
  grid-template-columns: repeat(4, 1fr);
package/src/example.scss CHANGED
@@ -20,7 +20,6 @@
20
20
  @import "components/Flex/styles/styles";
21
21
  @import "components/FlexItem/styles/styles";
22
22
  @import "components/Input/styles/styles";
23
- @import "components/Image/styles/styles";
24
23
  @import "components/Joystick/styles/styles";
25
24
  @import "components/Led/styles/mixins";
26
25
  @import "components/Led/styles/styles";
@@ -47,6 +46,9 @@
47
46
  @import "components/Badge/styles/styles";
48
47
  @import "components/Dropdown/styles/styles";
49
48
  @import "components/Skeleton/styles/styles";
49
+ @import "components/CoverEmpty/styles/styles";
50
+ @import "components/SegmentedControl/styles/styles";
51
+ @import "components/Stat/styles/styles";
50
52
  @import "example/styles/splash";
51
53
  @import "example/styles/styles";
52
54
  @import "example/styles/docs";
package/src/example.tsx CHANGED
@@ -5,12 +5,16 @@ import {
5
5
  Badge,
6
6
  Button,
7
7
  Card,
8
+ CoverEmpty,
8
9
  Dialog,
9
10
  Dropdown,
11
+ Icons,
10
12
  Input,
11
13
  Link,
12
14
  Logo,
15
+ SegmentedControl,
13
16
  Separator,
17
+ Stat,
14
18
  constants,
15
19
  } from "./index";
16
20
 
@@ -100,6 +104,16 @@ const SPACING = [
100
104
  ["$gb-sp-16", "64 px", "4rem", 64],
101
105
  ] as const;
102
106
 
107
+ const BREAKPOINTS = [
108
+ ["@include gx-phone", "< 768 px", "up to $g-tablet-width − 1px"],
109
+ [
110
+ "@include gx-tablet",
111
+ "768 – 1023 px",
112
+ "$g-tablet-width … $g-desktop-width − 1px",
113
+ ],
114
+ ["@include gx-desktop", "≥ 1024 px", "$g-desktop-width and up"],
115
+ ] as const;
116
+
103
117
  const RADIUS = [
104
118
  ["$gb-r-sm", "4 px", 4],
105
119
  ["$gb-r", "6 px", 6],
@@ -124,6 +138,29 @@ const ICONS: ReadonlyArray<readonly [string, string]> = [
124
138
  ["plus", "M12 5v14M5 12h14"],
125
139
  ];
126
140
 
141
+ const ICON_SET: ReadonlyArray<
142
+ readonly [string, (props: { className?: string }) => React.ReactElement]
143
+ > = [
144
+ ["IconSearch", Icons.IconSearch],
145
+ ["IconEdit", Icons.IconEdit],
146
+ ["IconTrash", Icons.IconTrash],
147
+ ["IconPlus", Icons.IconPlus],
148
+ ["IconRefresh", Icons.IconRefresh],
149
+ ["IconClock", Icons.IconClock],
150
+ ["IconImg", Icons.IconImg],
151
+ ["IconMail", Icons.IconMail],
152
+ ["IconCheck", Icons.IconCheck],
153
+ ["IconChevron", Icons.IconChevron],
154
+ ];
155
+
156
+ const SEGMENTED_OPTIONS = [
157
+ { value: "all", label: "All" },
158
+ { value: "active", label: "Active" },
159
+ { value: "archived", label: "Archived" },
160
+ ] as const;
161
+
162
+ type SegmentedValue = (typeof SEGMENTED_OPTIONS)[number]["value"];
163
+
127
164
  const NAV = [
128
165
  {
129
166
  title: "Getting started",
@@ -139,6 +176,7 @@ const NAV = [
139
176
  { id: "typography", label: "Typography" },
140
177
  { id: "spacing", label: "Spacing & radius" },
141
178
  { id: "iconography", label: "Iconography", count: "14" },
179
+ { id: "responsiveness", label: "Responsiveness" },
142
180
  ],
143
181
  },
144
182
  {
@@ -155,6 +193,9 @@ const NAV = [
155
193
  { id: "card", label: "Card" },
156
194
  { id: "badge", label: "Badge" },
157
195
  { id: "navigation", label: "Navigation" },
196
+ { id: "segmented-control", label: "Segmented control" },
197
+ { id: "stat", label: "Stat" },
198
+ { id: "cover-empty", label: "Cover empty" },
158
199
  ],
159
200
  },
160
201
  ];
@@ -165,6 +206,7 @@ const TOC = [
165
206
  ["typography", "Typography"],
166
207
  ["spacing", "Spacing & radius"],
167
208
  ["iconography", "Iconography"],
209
+ ["responsiveness", "Responsiveness"],
168
210
  ["logo", "Logo"],
169
211
  ["button", "Button"],
170
212
  ["link", "Link"],
@@ -176,6 +218,9 @@ const TOC = [
176
218
  ["card", "Card"],
177
219
  ["badge", "Badge"],
178
220
  ["navigation", "Navigation"],
221
+ ["segmented-control", "Segmented control"],
222
+ ["stat", "Stat"],
223
+ ["cover-empty", "Cover empty"],
179
224
  ] as const;
180
225
 
181
226
  type IconProps = {
@@ -368,6 +413,7 @@ function App() {
368
413
  const [installCopied, setInstallCopied] = useState(false);
369
414
  const [dropdownValue, setDropdownValue] = useState("red");
370
415
  const [isDialogOpen, setIsDialogOpen] = useState(false);
416
+ const [segmentedValue, setSegmentedValue] = useState<SegmentedValue>("all");
371
417
 
372
418
  useEffect(() => {
373
419
  document.documentElement.setAttribute("data-theme", theme);
@@ -663,6 +709,74 @@ function App() {
663
709
  <IconCell key={name} name={name} d={d} />
664
710
  ))}
665
711
  </div>
712
+ <h3 className="docs-subsection-title">Icon set</h3>
713
+ <p className="docs-section-desc">
714
+ Ready-to-use React icon components exported from{" "}
715
+ <code>Icons</code>. They inherit <code>currentColor</code>; size
716
+ and stroke are set by the consumer via a class name.
717
+ </p>
718
+ <div className="docs-icon-grid">
719
+ {ICON_SET.map(([name, IconComponent]) => (
720
+ <div className="docs-icon-cell docs-iconset-cell" key={name}>
721
+ <IconComponent className="docs-iconset-cell__svg" />
722
+ <span>{name}</span>
723
+ </div>
724
+ ))}
725
+ </div>
726
+ </section>
727
+
728
+ <section className="docs-section" id="responsiveness">
729
+ <div className="docs-section-eyebrow">Foundations / 05</div>
730
+ <h2 className="docs-section-title">Responsiveness</h2>
731
+ <p className="docs-section-desc">
732
+ Breakpoints are exposed as Sass variables and wrapped by three
733
+ mixins — <code>gx-phone</code>, <code>gx-tablet</code> and{" "}
734
+ <code>gx-desktop</code>. Each mixin scopes its block to exactly
735
+ one range, so the three never overlap. The demo below is styled
736
+ with the real mixins — resize the window to see the active range
737
+ light up.
738
+ </p>
739
+ <div className="docs-bp-grid" role="table" aria-label="Breakpoints">
740
+ {BREAKPOINTS.map(([mixin, range, vars]) => (
741
+ <div className="docs-bp-row" key={mixin}>
742
+ <span className="mixin">{mixin}</span>
743
+ <span className="range">{range}</span>
744
+ <span className="vars">{vars}</span>
745
+ </div>
746
+ ))}
747
+ </div>
748
+ <div className="docs-spacer-y" />
749
+ <Demo
750
+ stageClass="center"
751
+ code={`.docs-bp-demo .pill { opacity: 0.35; }
752
+
753
+ @include gx-phone {
754
+ .docs-bp-demo .pill--phone { opacity: 1; }
755
+ }
756
+
757
+ @include gx-tablet {
758
+ .docs-bp-demo .pill--tablet { opacity: 1; }
759
+ }
760
+
761
+ @include gx-desktop {
762
+ .docs-bp-demo .pill--desktop { opacity: 1; }
763
+ }`}
764
+ >
765
+ <div className="docs-bp-demo">
766
+ <div className="pill pill--phone">
767
+ <span className="nm">gx-phone</span>
768
+ <span className="rg">&lt; 768 px</span>
769
+ </div>
770
+ <div className="pill pill--tablet">
771
+ <span className="nm">gx-tablet</span>
772
+ <span className="rg">768 – 1023 px</span>
773
+ </div>
774
+ <div className="pill pill--desktop">
775
+ <span className="nm">gx-desktop</span>
776
+ <span className="rg">≥ 1024 px</span>
777
+ </div>
778
+ </div>
779
+ </Demo>
666
780
  </section>
667
781
 
668
782
  <section className="docs-section" id="logo">
@@ -1025,6 +1139,77 @@ function App() {
1025
1139
  </Demo>
1026
1140
  </section>
1027
1141
 
1142
+ <section className="docs-section" id="segmented-control">
1143
+ <div className="docs-section-eyebrow">Components / 12</div>
1144
+ <h2 className="docs-section-title">Segmented control</h2>
1145
+ <p className="docs-section-desc">
1146
+ A pill-shaped toggle for switching between a few mutually
1147
+ exclusive views. Prefer it over a dropdown when there are two to
1148
+ four options that benefit from being visible at once.
1149
+ </p>
1150
+ <Demo
1151
+ stageClass="center"
1152
+ code={`<SegmentedControl
1153
+ options={[
1154
+ { value: "all", label: "All" },
1155
+ { value: "active", label: "Active" },
1156
+ { value: "archived", label: "Archived" },
1157
+ ]}
1158
+ value="${segmentedValue}"
1159
+ onChange={setSegmentedValue}
1160
+ />`}
1161
+ >
1162
+ <SegmentedControl<SegmentedValue>
1163
+ options={SEGMENTED_OPTIONS}
1164
+ value={segmentedValue}
1165
+ onChange={setSegmentedValue}
1166
+ />
1167
+ </Demo>
1168
+ </section>
1169
+
1170
+ <section className="docs-section" id="stat">
1171
+ <div className="docs-section-eyebrow">Components / 13</div>
1172
+ <h2 className="docs-section-title">Stat</h2>
1173
+ <p className="docs-section-desc">
1174
+ A single key figure with a labelled, color-coded dot. Group a few
1175
+ of them to summarise the state of a page. The dot color is set via
1176
+ the <code>modifier</code> prop.
1177
+ </p>
1178
+ <Demo
1179
+ stageClass="column"
1180
+ code={`<Stat modifier="blue" label="Posts" value="128" />
1181
+ <Stat modifier="green" label="Published" value="94" />
1182
+ <Stat modifier="amber" label="Drafts" value="26" />
1183
+ <Stat modifier="red" label="Flagged" value="8" />`}
1184
+ >
1185
+ <div className="docs-stat-grid">
1186
+ <Stat modifier="blue" label="Posts" value="128" />
1187
+ <Stat modifier="green" label="Published" value="94" />
1188
+ <Stat modifier="amber" label="Drafts" value="26" />
1189
+ <Stat modifier="red" label="Flagged" value="8" />
1190
+ </div>
1191
+ </Demo>
1192
+ </section>
1193
+
1194
+ <section className="docs-section" id="cover-empty">
1195
+ <div className="docs-section-eyebrow">Components / 14</div>
1196
+ <h2 className="docs-section-title">Cover empty</h2>
1197
+ <p className="docs-section-desc">
1198
+ A neutral placeholder for a missing image or empty media slot. It
1199
+ fills its container, so give the parent a fixed size.
1200
+ </p>
1201
+ <Demo
1202
+ stageClass="center"
1203
+ code={`<div style={{ width: 160, height: 120 }}>
1204
+ <CoverEmpty />
1205
+ </div>`}
1206
+ >
1207
+ <div className="docs-cover-empty-stage">
1208
+ <CoverEmpty />
1209
+ </div>
1210
+ </Demo>
1211
+ </section>
1212
+
1028
1213
  <footer className="docs-footer">
1029
1214
  <div>graphen · {VERSION} · MIT</div>
1030
1215
  <div>Built by the CODA_ team</div>