package fiesta import ( "embed" "io/fs" "sort" "strings" ) // Version is the design system's own version, the one the release // build stamps on fiesta.css and the site serves under //. const Version = "1.2.0" // tree is the design system's files as shipped: the licence, the CSS, // the optional scripts, the icons, the examples and the interface // strings. go:embed names directories, so a file added under one is // embedded without a change here. // //go:embed LICENSE css js icons examples i18n var tree embed.FS // FS returns the embedded tree. css/tokens.css, js/busy.js, // icons/check.svg and LICENSE are paths in it, so a server can mount // it as it is and a build can walk it. func FS() fs.FS { return tree } // read returns one file from the tree. Every path passed here is a // file this package embeds by name, so a miss is a broken build rather // than a condition to report: the panic says which file. func read(path string) []byte { b, err := tree.ReadFile(path) if err != nil { panic("fiesta: " + path + " is not embedded: " + err.Error()) } return b } // TokensCSS returns css/tokens.css: the structural stylesheet, every // component's layout and behaviour, painted with whichever theme sits // beside it. func TokensCSS() []byte { return read("css/tokens.css") } // ShellCSS returns css/shell.css: the two application shells' own // rules, which a page links only when it uses a shell. func ShellCSS() []byte { return read("css/shell.css") } // License returns LICENSE, the Mozilla Public License 2.0 the design // system ships under. The icons carry Lucide's ISC notice as well, at // icons/LICENSE-lucide.txt in FS(). func License() []byte { return read("LICENSE") } // themeNames lists the shipped themes, day first: it is the default // and the reference theme, the one the other themes' token sets are // checked against. The slice matches the files in css/themes/ exactly; // adding a theme means adding both. // // day the everyday default: an ordinary blue on white and grey // plain the skeleton: greyscale, system type, almost no shape // signal graphite and one live cobalt; milled corners, dense shadows var themeNames = []string{"day", "plain", "signal"} // ThemeNames returns the shipped theme names, day first. The returned // slice is a copy, so a caller sorting or truncating it cannot reorder // the library's own list. func ThemeNames() []string { return append([]string(nil), themeNames...) } // ThemeCSS returns one theme's bytes, the colour, type-family and // shape tokens that tokens.css paints its components with, reporting // false for a name that is not shipped. func ThemeCSS(name string) ([]byte, bool) { b, err := tree.ReadFile("css/themes/" + name + ".css") return b, err == nil } // scriptNames lists the optional scripts in the order a page that // wants them all would load them, deferred: fiesta.js first, since it // restores a remembered colour scheme and owns the menus every page // has, busy and light next, since they touch every button, the shells' // navigation after that, then the snippet's Copy button and the // controls, and calendar.js last because it is the grid datetime.js // asks it for. transition.js is the one script that is not deferred: a // page loads it before its stylesheets, since the browser can reject a // skipped transition before a deferred script runs. It is listed // beside shell.js because it does shell.js's transition work on a page // without a shell. The slice matches the files in js/ exactly. var scriptNames = []string{"fiesta.js", "busy.js", "light.js", "anchor.js", "shell.js", "transition.js", "copy.js", "select.js", "datetime.js", "calendar.js"} // ScriptNames returns the optional scripts' file names, in load order. // The returned slice is a copy. func ScriptNames() []string { return append([]string(nil), scriptNames...) } // Script returns one optional script's bytes by its file name, // reporting false for a name that is not shipped. func Script(name string) ([]byte, bool) { b, err := tree.ReadFile("js/" + name) return b, err == nil } // languageNames lists the shipped languages, en first: one // i18n/.json each, holding the strings datetime.js, select.js // and copy.js put on screen. The slice matches the files in i18n/ // exactly; adding a language means adding both. Plain pt is not a // language here: it is a test alias for pt-PT // (check/testdata/datetime/retagged.json), kept for a page whose lang // still says pt. var languageNames = []string{"en", "ar", "bn", "es", "ga", "hi", "ja", "pt-PT", "ru", "vi", "yue", "zh-Hans"} // Languages returns the shipped languages' tags, en first. The // returned slice is a copy. func Languages() []string { return append([]string(nil), languageNames...) } // Strings returns one language's table, i18n/.json: a flat JSON // object whose keys are the string attributes' names with data-rst- // removed, plus date-words, the parser's vocabulary. It reports false // for a tag that is not shipped; a page's tag is matched to a shipped // one by the scripts themselves (fiestaPickLang), not here. func Strings(lang string) ([]byte, bool) { b, err := tree.ReadFile("i18n/" + lang + ".json") return b, err == nil } // IconSlugs lists every icon Icon answers, sorted. The names are the // design system's own vocabulary, which is Lucide's with five renamed // (kebab for ellipsis-vertical; check-circle, alert-triangle, x-circle // and help-circle for their Lucide v1 names). func IconSlugs() []string { entries, err := tree.ReadDir("icons") if err != nil { panic("fiesta: icons/ is not embedded: " + err.Error()) } var out []string for _, e := range entries { if name := e.Name(); strings.HasSuffix(name, ".svg") { out = append(out, strings.TrimSuffix(name, ".svg")) } } sort.Strings(out) return out } // Icon returns one icon by its slug, in the inline form: an with // no xmlns, class="icon", a 24x24 viewBox, currentColor strokes and // aria-hidden="true", ready to paste into HTML beside its own visible // label. It reports false for a slug that is not shipped. func Icon(slug string) ([]byte, bool) { b, err := tree.ReadFile("icons/" + slug + ".svg") return b, err == nil }