graphen 2.0.3 → 2.0.4

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.4",
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;
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
 
@@ -124,6 +128,29 @@ const ICONS: ReadonlyArray<readonly [string, string]> = [
124
128
  ["plus", "M12 5v14M5 12h14"],
125
129
  ];
126
130
 
131
+ const ICON_SET: ReadonlyArray<
132
+ readonly [string, (props: { className?: string }) => React.ReactElement]
133
+ > = [
134
+ ["IconSearch", Icons.IconSearch],
135
+ ["IconEdit", Icons.IconEdit],
136
+ ["IconTrash", Icons.IconTrash],
137
+ ["IconPlus", Icons.IconPlus],
138
+ ["IconRefresh", Icons.IconRefresh],
139
+ ["IconClock", Icons.IconClock],
140
+ ["IconImg", Icons.IconImg],
141
+ ["IconMail", Icons.IconMail],
142
+ ["IconCheck", Icons.IconCheck],
143
+ ["IconChevron", Icons.IconChevron],
144
+ ];
145
+
146
+ const SEGMENTED_OPTIONS = [
147
+ { value: "all", label: "All" },
148
+ { value: "active", label: "Active" },
149
+ { value: "archived", label: "Archived" },
150
+ ] as const;
151
+
152
+ type SegmentedValue = (typeof SEGMENTED_OPTIONS)[number]["value"];
153
+
127
154
  const NAV = [
128
155
  {
129
156
  title: "Getting started",
@@ -155,6 +182,9 @@ const NAV = [
155
182
  { id: "card", label: "Card" },
156
183
  { id: "badge", label: "Badge" },
157
184
  { id: "navigation", label: "Navigation" },
185
+ { id: "segmented-control", label: "Segmented control" },
186
+ { id: "stat", label: "Stat" },
187
+ { id: "cover-empty", label: "Cover empty" },
158
188
  ],
159
189
  },
160
190
  ];
@@ -176,6 +206,9 @@ const TOC = [
176
206
  ["card", "Card"],
177
207
  ["badge", "Badge"],
178
208
  ["navigation", "Navigation"],
209
+ ["segmented-control", "Segmented control"],
210
+ ["stat", "Stat"],
211
+ ["cover-empty", "Cover empty"],
179
212
  ] as const;
180
213
 
181
214
  type IconProps = {
@@ -368,6 +401,7 @@ function App() {
368
401
  const [installCopied, setInstallCopied] = useState(false);
369
402
  const [dropdownValue, setDropdownValue] = useState("red");
370
403
  const [isDialogOpen, setIsDialogOpen] = useState(false);
404
+ const [segmentedValue, setSegmentedValue] = useState<SegmentedValue>("all");
371
405
 
372
406
  useEffect(() => {
373
407
  document.documentElement.setAttribute("data-theme", theme);
@@ -663,6 +697,20 @@ function App() {
663
697
  <IconCell key={name} name={name} d={d} />
664
698
  ))}
665
699
  </div>
700
+ <h3 className="docs-subsection-title">Icon set</h3>
701
+ <p className="docs-section-desc">
702
+ Ready-to-use React icon components exported from{" "}
703
+ <code>Icons</code>. They inherit <code>currentColor</code>; size
704
+ and stroke are set by the consumer via a class name.
705
+ </p>
706
+ <div className="docs-icon-grid">
707
+ {ICON_SET.map(([name, IconComponent]) => (
708
+ <div className="docs-icon-cell docs-iconset-cell" key={name}>
709
+ <IconComponent className="docs-iconset-cell__svg" />
710
+ <span>{name}</span>
711
+ </div>
712
+ ))}
713
+ </div>
666
714
  </section>
667
715
 
668
716
  <section className="docs-section" id="logo">
@@ -1025,6 +1073,77 @@ function App() {
1025
1073
  </Demo>
1026
1074
  </section>
1027
1075
 
1076
+ <section className="docs-section" id="segmented-control">
1077
+ <div className="docs-section-eyebrow">Components / 12</div>
1078
+ <h2 className="docs-section-title">Segmented control</h2>
1079
+ <p className="docs-section-desc">
1080
+ A pill-shaped toggle for switching between a few mutually
1081
+ exclusive views. Prefer it over a dropdown when there are two to
1082
+ four options that benefit from being visible at once.
1083
+ </p>
1084
+ <Demo
1085
+ stageClass="center"
1086
+ code={`<SegmentedControl
1087
+ options={[
1088
+ { value: "all", label: "All" },
1089
+ { value: "active", label: "Active" },
1090
+ { value: "archived", label: "Archived" },
1091
+ ]}
1092
+ value="${segmentedValue}"
1093
+ onChange={setSegmentedValue}
1094
+ />`}
1095
+ >
1096
+ <SegmentedControl<SegmentedValue>
1097
+ options={SEGMENTED_OPTIONS}
1098
+ value={segmentedValue}
1099
+ onChange={setSegmentedValue}
1100
+ />
1101
+ </Demo>
1102
+ </section>
1103
+
1104
+ <section className="docs-section" id="stat">
1105
+ <div className="docs-section-eyebrow">Components / 13</div>
1106
+ <h2 className="docs-section-title">Stat</h2>
1107
+ <p className="docs-section-desc">
1108
+ A single key figure with a labelled, color-coded dot. Group a few
1109
+ of them to summarise the state of a page. The dot color is set via
1110
+ the <code>modifier</code> prop.
1111
+ </p>
1112
+ <Demo
1113
+ stageClass="column"
1114
+ code={`<Stat modifier="blue" label="Posts" value="128" />
1115
+ <Stat modifier="green" label="Published" value="94" />
1116
+ <Stat modifier="amber" label="Drafts" value="26" />
1117
+ <Stat modifier="red" label="Flagged" value="8" />`}
1118
+ >
1119
+ <div className="docs-stat-grid">
1120
+ <Stat modifier="blue" label="Posts" value="128" />
1121
+ <Stat modifier="green" label="Published" value="94" />
1122
+ <Stat modifier="amber" label="Drafts" value="26" />
1123
+ <Stat modifier="red" label="Flagged" value="8" />
1124
+ </div>
1125
+ </Demo>
1126
+ </section>
1127
+
1128
+ <section className="docs-section" id="cover-empty">
1129
+ <div className="docs-section-eyebrow">Components / 14</div>
1130
+ <h2 className="docs-section-title">Cover empty</h2>
1131
+ <p className="docs-section-desc">
1132
+ A neutral placeholder for a missing image or empty media slot. It
1133
+ fills its container, so give the parent a fixed size.
1134
+ </p>
1135
+ <Demo
1136
+ stageClass="center"
1137
+ code={`<div style={{ width: 160, height: 120 }}>
1138
+ <CoverEmpty />
1139
+ </div>`}
1140
+ >
1141
+ <div className="docs-cover-empty-stage">
1142
+ <CoverEmpty />
1143
+ </div>
1144
+ </Demo>
1145
+ </section>
1146
+
1028
1147
  <footer className="docs-footer">
1029
1148
  <div>graphen · {VERSION} · MIT</div>
1030
1149
  <div>Built by the CODA_ team</div>
package/src/index.ts CHANGED
@@ -4,8 +4,8 @@ import Button from "src/components/Button";
4
4
  import Card from "src/components/Card";
5
5
  import Dialog from "src/components/Dialog";
6
6
  import Icon from "src/components/Icon";
7
+ import * as Icons from "src/components/Icons";
7
8
  import Input from "src/components/Input";
8
- import Image from "src/components/Image";
9
9
  import Joystick from "src/components/Joystick";
10
10
  import Link from "src/components/Link";
11
11
  import Loader from "src/components/Loader";
@@ -23,6 +23,9 @@ import PanelFooter from "src/components/PanelFooter";
23
23
  import PanelTitle from "src/components/PanelTitle";
24
24
  import Switch from "src/components/Switch";
25
25
  import Skeleton from "src/components/Skeleton";
26
+ import CoverEmpty from "src/components/CoverEmpty";
27
+ import SegmentedControl from "src/components/SegmentedControl";
28
+ import Stat from "src/components/Stat";
26
29
  import * as constants from "src/variables/constants";
27
30
 
28
31
  export {
@@ -32,6 +35,7 @@ export {
32
35
  Card,
33
36
  Dialog,
34
37
  Icon,
38
+ Icons,
35
39
  Input,
36
40
  Joystick,
37
41
  Link,
@@ -41,7 +45,6 @@ export {
41
45
  Tooltip,
42
46
  Validation,
43
47
  Logo,
44
- Image,
45
48
  Dropdown,
46
49
  Flex,
47
50
  FlexItem,
@@ -51,5 +54,11 @@ export {
51
54
  PanelTitle,
52
55
  Switch,
53
56
  Skeleton,
57
+ CoverEmpty,
58
+ SegmentedControl,
59
+ Stat,
54
60
  constants,
55
61
  };
62
+
63
+ export type { SegmentedControlOption } from "src/components/SegmentedControl";
64
+ export type { StatModifier } from "src/components/Stat";
package/src/style.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,3 +46,6 @@
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";
@@ -1,26 +0,0 @@
1
- import React from "react";
2
- import classNames from "classnames";
3
-
4
- type Props = {
5
- src: string;
6
- className?: string;
7
- height?: number;
8
- width?: number;
9
- };
10
-
11
- function Image({ className = "", height = 200, width = 200, src }: Props) {
12
- const classes = classNames("gc-image", className);
13
-
14
- return (
15
- <object
16
- className={classes}
17
- data={src}
18
- type="image/jpg"
19
- style={{ height, width }}
20
- >
21
- <div className="gc-image__fallback" />
22
- </object>
23
- );
24
- }
25
-
26
- export default Image;
@@ -1,20 +0,0 @@
1
- .gc-image {
2
- position: relative;
3
- display: inline-block;
4
- border: 1px solid $gb-color-component-dark;
5
- padding: $radius-medium;
6
- vertical-align: middle;
7
-
8
- &__fallback {
9
- background-color: $gb-color-component-light;
10
- display: flex;
11
- justify-content: center;
12
- flex-direction: column;
13
- height: 100%;
14
-
15
- &::before {
16
- content: "No image loaded";
17
- text-align: center;
18
- }
19
- }
20
- }