Foundation: tokens, theme, icons, i18n, Button, Spinner, gallery scaffold
This commit is contained in:
95 files changed
+2489
No files matched your search
@@ -0,0 +1,28 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
// Contrast returns the WCAG contrast ratio of two opaque colors.
|
||||
func Contrast(a, b ui.Color) float64 {
|
||||
la, lb := luminance(a), luminance(b)
|
||||
if la < lb {
|
||||
la, lb = lb, la
|
||||
}
|
||||
return (la + 0.05) / (lb + 0.05)
|
||||
}
|
||||
|
||||
func luminance(c ui.Color) float64 {
|
||||
return 0.2126*channel(c.R) + 0.7152*channel(c.G) + 0.0722*channel(c.B)
|
||||
}
|
||||
|
||||
func channel(v uint8) float64 {
|
||||
s := float64(v) / 255
|
||||
if s <= 0.04045 {
|
||||
return s / 12.92
|
||||
}
|
||||
return math.Pow((s+0.055)/1.055, 2.4)
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
// Package theme holds MujicaUI's design tokens: colors, type, metrics and motion.
|
||||
package theme
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
// Tokens are the colors of one appearance.
|
||||
type Tokens struct {
|
||||
Dark bool
|
||||
Background ui.Color
|
||||
Surface ui.Color
|
||||
SurfaceHover ui.Color
|
||||
Text ui.Color
|
||||
TextMuted ui.Color
|
||||
Border ui.Color
|
||||
ControlBorder ui.Color
|
||||
Accent ui.Color
|
||||
AccentHover ui.Color
|
||||
AccentPressed ui.Color
|
||||
OnAccent ui.Color
|
||||
AccentText ui.Color
|
||||
Selection ui.Color
|
||||
Focus ui.Color
|
||||
Ornament ui.Color
|
||||
Success ui.Color
|
||||
Warning ui.Color
|
||||
Danger ui.Color
|
||||
Info ui.Color
|
||||
DangerFill ui.Color
|
||||
OnDanger ui.Color
|
||||
}
|
||||
|
||||
// Light returns the ivory appearance.
|
||||
func Light() Tokens {
|
||||
return Tokens{
|
||||
Background: ui.Hex("#F2EBDD"),
|
||||
Surface: ui.Hex("#FBF7EF"),
|
||||
SurfaceHover: ui.Hex("#E8DFD0"),
|
||||
Text: ui.Hex("#211C1D"),
|
||||
TextMuted: ui.Hex("#6A5B55"),
|
||||
Border: ui.Hex("#D2C3AF"),
|
||||
ControlBorder: ui.Hex("#877662"),
|
||||
Accent: ui.Hex("#7D2034"),
|
||||
AccentHover: ui.Hex("#651729"),
|
||||
AccentPressed: ui.Hex("#511020"),
|
||||
OnAccent: ui.Hex("#FFF8EE"),
|
||||
AccentText: ui.Hex("#7D2034"),
|
||||
Selection: ui.Hex("#EAD7D9"),
|
||||
Focus: ui.Hex("#7D2034"),
|
||||
Ornament: ui.Hex("#AD9164"),
|
||||
Success: ui.Hex("#326247"),
|
||||
Warning: ui.Hex("#805714"),
|
||||
Danger: ui.Hex("#A12635"),
|
||||
Info: ui.Hex("#355E80"),
|
||||
DangerFill: ui.Hex("#A12635"),
|
||||
OnDanger: ui.Hex("#FFF8EE"),
|
||||
}
|
||||
}
|
||||
|
||||
// Dark returns the charcoal appearance.
|
||||
func Dark() Tokens {
|
||||
return Tokens{
|
||||
Dark: true,
|
||||
Background: ui.Hex("#191618"),
|
||||
Surface: ui.Hex("#242024"),
|
||||
SurfaceHover: ui.Hex("#342C32"),
|
||||
Text: ui.Hex("#F2EBDD"),
|
||||
TextMuted: ui.Hex("#BCAEAA"),
|
||||
Border: ui.Hex("#51424A"),
|
||||
ControlBorder: ui.Hex("#968278"),
|
||||
Accent: ui.Hex("#9C3C53"),
|
||||
AccentHover: ui.Hex("#AA425C"),
|
||||
AccentPressed: ui.Hex("#813047"),
|
||||
OnAccent: ui.Hex("#FFF8EE"),
|
||||
AccentText: ui.Hex("#E4A0AE"),
|
||||
Selection: ui.Hex("#492A36"),
|
||||
Focus: ui.Hex("#E4A0AE"),
|
||||
Ornament: ui.Hex("#BDA172"),
|
||||
Success: ui.Hex("#94C6A4"),
|
||||
Warning: ui.Hex("#DFC080"),
|
||||
Danger: ui.Hex("#F0A1AA"),
|
||||
Info: ui.Hex("#9DBDDA"),
|
||||
DangerFill: ui.Hex("#A12635"),
|
||||
OnDanger: ui.Hex("#FFF8EE"),
|
||||
}
|
||||
}
|
||||
|
||||
// HighContrast returns t with secondary text and borders raised for readability.
|
||||
func (t Tokens) HighContrast() Tokens {
|
||||
t.TextMuted = t.Text
|
||||
t.Border = t.ControlBorder
|
||||
return t
|
||||
}
|
||||
|
||||
// Density sets control heights and the spacing unit.
|
||||
type Density int
|
||||
|
||||
const (
|
||||
Default Density = iota
|
||||
Compact
|
||||
Comfortable
|
||||
)
|
||||
|
||||
// Unit is the base spacing unit in DIPs.
|
||||
func (d Density) Unit() float32 {
|
||||
switch d {
|
||||
case Compact:
|
||||
return 3
|
||||
case Comfortable:
|
||||
return 5
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
// ControlHeight is the minimum height of single-line controls and data rows.
|
||||
func (d Density) ControlHeight() float32 {
|
||||
switch d {
|
||||
case Compact:
|
||||
return 28
|
||||
case Comfortable:
|
||||
return 44
|
||||
}
|
||||
return 36
|
||||
}
|
||||
|
||||
func (d Density) String() string {
|
||||
switch d {
|
||||
case Compact:
|
||||
return "Compact"
|
||||
case Comfortable:
|
||||
return "Comfortable"
|
||||
}
|
||||
return "Default"
|
||||
}
|
||||
|
||||
// Type sizes and families.
|
||||
const (
|
||||
H1Size float32 = 28
|
||||
H2Size float32 = 22
|
||||
H3Size float32 = 18
|
||||
BodySize float32 = 14
|
||||
CaptionSize float32 = 12
|
||||
LineHeight float32 = 1.5
|
||||
|
||||
SerifFont = "Georgia, Songti SC, SimSun, Noto Serif CJK SC, serif"
|
||||
MonoFont = "monospace"
|
||||
)
|
||||
|
||||
// Shape metrics in DIPs.
|
||||
const (
|
||||
ControlRadius float32 = 2
|
||||
CardRadius float32 = 4
|
||||
PopupRadius float32 = 6
|
||||
BorderWidth float32 = 1
|
||||
FocusWidth float32 = 2
|
||||
FocusGap float32 = 2
|
||||
MinHitSize float32 = 28
|
||||
)
|
||||
|
||||
// Motion durations.
|
||||
const (
|
||||
StateDuration = 120 * time.Millisecond
|
||||
PopupDuration = 160 * time.Millisecond
|
||||
)
|
||||
@@ -0,0 +1,59 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
func TestContrastFloors(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tokens Tokens
|
||||
}{{"light", Light()}, {"dark", Dark()}} {
|
||||
k := tc.tokens
|
||||
text := map[string][2]ui.Color{
|
||||
"Text/Background": {k.Text, k.Background},
|
||||
"Text/Surface": {k.Text, k.Surface},
|
||||
"Text/SurfaceHover": {k.Text, k.SurfaceHover},
|
||||
"Text/Selection": {k.Text, k.Selection},
|
||||
"TextMuted/Background": {k.TextMuted, k.Background},
|
||||
"TextMuted/Surface": {k.TextMuted, k.Surface},
|
||||
"OnAccent/Accent": {k.OnAccent, k.Accent},
|
||||
"OnAccent/AccentHover": {k.OnAccent, k.AccentHover},
|
||||
"OnAccent/Pressed": {k.OnAccent, k.AccentPressed},
|
||||
"OnDanger/DangerFill": {k.OnDanger, k.DangerFill},
|
||||
"AccentText/Surface": {k.AccentText, k.Surface},
|
||||
"Success/Surface": {k.Success, k.Surface},
|
||||
"Warning/Surface": {k.Warning, k.Surface},
|
||||
"Danger/Surface": {k.Danger, k.Surface},
|
||||
"Info/Surface": {k.Info, k.Surface},
|
||||
}
|
||||
for pair, c := range text {
|
||||
if r := Contrast(c[0], c[1]); r < 4.5 {
|
||||
t.Errorf("%s %s = %.2f, want >= 4.5", tc.name, pair, r)
|
||||
}
|
||||
}
|
||||
graphic := map[string][2]ui.Color{
|
||||
"ControlBorder/Background": {k.ControlBorder, k.Background},
|
||||
"ControlBorder/Surface": {k.ControlBorder, k.Surface},
|
||||
"Focus/Background": {k.Focus, k.Background},
|
||||
}
|
||||
for pair, c := range graphic {
|
||||
if r := Contrast(c[0], c[1]); r < 3 {
|
||||
t.Errorf("%s %s = %.2f, want >= 3", tc.name, pair, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDensity(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
d Density
|
||||
unit, height float32
|
||||
}{{Compact, 3, 28}, {Default, 4, 36}, {Comfortable, 5, 44}} {
|
||||
if tc.d.Unit() != tc.unit || tc.d.ControlHeight() != tc.height {
|
||||
t.Errorf("%v: unit %v height %v", tc.d, tc.d.Unit(), tc.d.ControlHeight())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user