| 1 | package fiesta |
| 2 | |
| 3 | import ( |
| 4 | "embed" |
| 5 | "io/fs" |
| 6 | "sort" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | // Version is the design system's own version, the one the release |
| 11 | // build stamps on fiesta.css and the site serves under /<version>/. |
| 12 | const Version = "1.2.0" |
| 13 | |
| 14 | // tree is the design system's files as shipped: the licence, the CSS, |
| 15 | // the optional scripts, the icons, the examples and the interface |
| 16 | // strings. go:embed names directories, so a file added under one is |
| 17 | // embedded without a change here. |
| 18 | // |
| 19 | //go:embed LICENSE css js icons examples i18n |
| 20 | var tree embed.FS |
| 21 | |
| 22 | // FS returns the embedded tree. css/tokens.css, js/busy.js, |
| 23 | // icons/check.svg and LICENSE are paths in it, so a server can mount |
| 24 | // it as it is and a build can walk it. |
| 25 | func FS() fs.FS { return tree } |
| 26 | |
| 27 | // read returns one file from the tree. Every path passed here is a |
| 28 | // file this package embeds by name, so a miss is a broken build rather |
| 29 | // than a condition to report: the panic says which file. |
| 30 | func read(path string) []byte { |
| 31 | b, err := tree.ReadFile(path) |
| 32 | if err != nil { |
| 33 | panic("fiesta: " + path + " is not embedded: " + err.Error()) |
| 34 | } |
| 35 | return b |
| 36 | } |
| 37 | |
| 38 | // TokensCSS returns css/tokens.css: the structural stylesheet, every |
| 39 | // component's layout and behaviour, painted with whichever theme sits |
| 40 | // beside it. |
| 41 | func TokensCSS() []byte { return read("css/tokens.css") } |
| 42 | |
| 43 | // ShellCSS returns css/shell.css: the two application shells' own |
| 44 | // rules, which a page links only when it uses a shell. |
| 45 | func ShellCSS() []byte { return read("css/shell.css") } |
| 46 | |
| 47 | // License returns LICENSE, the Mozilla Public License 2.0 the design |
| 48 | // system ships under. The icons carry Lucide's ISC notice as well, at |
| 49 | // icons/LICENSE-lucide.txt in FS(). |
| 50 | func License() []byte { return read("LICENSE") } |
| 51 | |
| 52 | // themeNames lists the shipped themes, day first: it is the default |
| 53 | // and the reference theme, the one the other themes' token sets are |
| 54 | // checked against. The slice matches the files in css/themes/ exactly; |
| 55 | // adding a theme means adding both. |
| 56 | // |
| 57 | // day the everyday default: an ordinary blue on white and grey |
| 58 | // plain the skeleton: greyscale, system type, almost no shape |
| 59 | // signal graphite and one live cobalt; milled corners, dense shadows |
| 60 | var themeNames = []string{"day", "plain", "signal"} |
| 61 | |
| 62 | // ThemeNames returns the shipped theme names, day first. The returned |
| 63 | // slice is a copy, so a caller sorting or truncating it cannot reorder |
| 64 | // the library's own list. |
| 65 | func ThemeNames() []string { return append([]string(nil), themeNames...) } |
| 66 | |
| 67 | // ThemeCSS returns one theme's bytes, the colour, type-family and |
| 68 | // shape tokens that tokens.css paints its components with, reporting |
| 69 | // false for a name that is not shipped. |
| 70 | func ThemeCSS(name string) ([]byte, bool) { |
| 71 | b, err := tree.ReadFile("css/themes/" + name + ".css") |
| 72 | return b, err == nil |
| 73 | } |
| 74 | |
| 75 | // scriptNames lists the optional scripts in the order a page that |
| 76 | // wants them all would load them, deferred: fiesta.js first, since it |
| 77 | // restores a remembered colour scheme and owns the menus every page |
| 78 | // has, busy and light next, since they touch every button, the shells' |
| 79 | // navigation after that, then the snippet's Copy button and the |
| 80 | // controls, and calendar.js last because it is the grid datetime.js |
| 81 | // asks it for. transition.js is the one script that is not deferred: a |
| 82 | // page loads it before its stylesheets, since the browser can reject a |
| 83 | // skipped transition before a deferred script runs. It is listed |
| 84 | // beside shell.js because it does shell.js's transition work on a page |
| 85 | // without a shell. The slice matches the files in js/ exactly. |
| 86 | var scriptNames = []string{"fiesta.js", "busy.js", "light.js", "anchor.js", "shell.js", "transition.js", "copy.js", "select.js", "datetime.js", "calendar.js"} |
| 87 | |
| 88 | // ScriptNames returns the optional scripts' file names, in load order. |
| 89 | // The returned slice is a copy. |
| 90 | func ScriptNames() []string { return append([]string(nil), scriptNames...) } |
| 91 | |
| 92 | // Script returns one optional script's bytes by its file name, |
| 93 | // reporting false for a name that is not shipped. |
| 94 | func Script(name string) ([]byte, bool) { |
| 95 | b, err := tree.ReadFile("js/" + name) |
| 96 | return b, err == nil |
| 97 | } |
| 98 | |
| 99 | // languageNames lists the shipped languages, en first: one |
| 100 | // i18n/<lang>.json each, holding the strings datetime.js, select.js |
| 101 | // and copy.js put on screen. The slice matches the files in i18n/ |
| 102 | // exactly; adding a language means adding both. Plain pt is not a |
| 103 | // language here: it is a test alias for pt-PT |
| 104 | // (check/testdata/datetime/retagged.json), kept for a page whose lang |
| 105 | // still says pt. |
| 106 | var languageNames = []string{"en", "ar", "bn", "es", "ga", "hi", "ja", "pt-PT", "ru", "vi", "yue", "zh-Hans"} |
| 107 | |
| 108 | // Languages returns the shipped languages' tags, en first. The |
| 109 | // returned slice is a copy. |
| 110 | func Languages() []string { return append([]string(nil), languageNames...) } |
| 111 | |
| 112 | // Strings returns one language's table, i18n/<lang>.json: a flat JSON |
| 113 | // object whose keys are the string attributes' names with data-rst- |
| 114 | // removed, plus date-words, the parser's vocabulary. It reports false |
| 115 | // for a tag that is not shipped; a page's tag is matched to a shipped |
| 116 | // one by the scripts themselves (fiestaPickLang), not here. |
| 117 | func Strings(lang string) ([]byte, bool) { |
| 118 | b, err := tree.ReadFile("i18n/" + lang + ".json") |
| 119 | return b, err == nil |
| 120 | } |
| 121 | |
| 122 | // IconSlugs lists every icon Icon answers, sorted. The names are the |
| 123 | // design system's own vocabulary, which is Lucide's with five renamed |
| 124 | // (kebab for ellipsis-vertical; check-circle, alert-triangle, x-circle |
| 125 | // and help-circle for their Lucide v1 names). |
| 126 | func IconSlugs() []string { |
| 127 | entries, err := tree.ReadDir("icons") |
| 128 | if err != nil { |
| 129 | panic("fiesta: icons/ is not embedded: " + err.Error()) |
| 130 | } |
| 131 | var out []string |
| 132 | for _, e := range entries { |
| 133 | if name := e.Name(); strings.HasSuffix(name, ".svg") { |
| 134 | out = append(out, strings.TrimSuffix(name, ".svg")) |
| 135 | } |
| 136 | } |
| 137 | sort.Strings(out) |
| 138 | return out |
| 139 | } |
| 140 | |
| 141 | // Icon returns one icon by its slug, in the inline form: an <svg> with |
| 142 | // no xmlns, class="icon", a 24x24 viewBox, currentColor strokes and |
| 143 | // aria-hidden="true", ready to paste into HTML beside its own visible |
| 144 | // label. It reports false for a slug that is not shipped. |
| 145 | func Icon(slug string) ([]byte, bool) { |
| 146 | b, err := tree.ReadFile("icons/" + slug + ".svg") |
| 147 | return b, err == nil |
| 148 | } |
| 149 | |