Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

Looks — a site's whole design, picked per version

abstract component Hero(string title, …); · component AtelierHero(string title, …) : Hero { render { … } } · look Atelier { Label = "Atelier"; Theme = Gallery; } · app.Look = Atelier;

A LOOK is a site's whole design — its frame, its pages, its theme and fonts, its design of every block type — declared in code (normally in a look KIT) and picked per site version at the page desk. Block types and page parts are CONTRACTS (`abstract component`), each look implements them with exactly the contract's parameters, and what staff arrange is stored as the type and its values — so switching a version's look redraws the same content in the new design.

stable3 examples compiled by CIuiblockscontenttheming

Source on GitHub: Osysharp.Site.Looks — github.com/osysharp/site/looks ↗

Summary#

A store that wants a new design should not need a new app. Write the design as a look: the frame around every page, the front page and the other pages a customer walks through, the theme and fonts, and a design of every block staff arrange. Install several looks — five shop designs, say — and staff pick one per version of the site, at the page desk: drafted, previewed as a whole site, switched on a date. Orders, stock, customers and everything staff arranged stay put; only the design changes.

// a contract — the shape, no design (a contract kit's, or the app's)
[Block("Hero")] public abstract component Hero([Label("Headline")] string title, Picture photo = "");

// a look — one design (a look kit's namespace)
namespace Osysharp.Shop.Looks.Atelier;
look Atelier { Label = "Atelier"; Theme = Gallery; }
component AtelierHero(string title, Picture photo = "") : Hero { render { … } }

// the app — installs looks, names the one it wears until staff choose
app.Look = Atelier;

Signature#

[Block("Label")]                       // optional: a BLOCK TYPE staff place in a region
public abstract component Name(params);   // a contract: parameters, no body

component Design(params) : Name { render { … } }  // a look's design of it — exactly the contract's parameters

look Name { Label = "…"; Hint = "…"; Theme = SomeTheme; }   // in a namespace of its own

app.Look = Name;                       // the look the site wears where a version names none

Description#

What is a contract? — abstract component#

An abstract component has parameters and no body: it is the SHAPE of something a look draws. With [Block("…")] it is a block type — a hero, a quote, a row of products — which staff place in a region and fill in at the desk, exactly like a block (Blocks and regions — pages the staff arrange): its parameters are the fields, their types choose the editors, [Label]/[Hint]/[Sample] name them. Without [Block] it is a part a page places — the frame around every page, the front page, a listing.

Anywhere a contract is called — a page, a layout, a region's arrangement — the compiler draws the design of the look the site is wearing, handing on every parameter and every slot.

What is a design? — component X(…) : Contract#

A component that writes : Hero is a DESIGN of the contract. It takes exactly the contract's parameters — the same names, the same types, in the same order — and is refused otherwise, like a class against an interface, naming the difference:

  • "'NightHero' implements Hero, whose 'photo' is a Picture, not a string."
  • "'NightHero' implements Hero, and leaves out its parameter 'ground'."
  • "'NightHero' implements Hero, which has no parameter 'moon' — … Something only this look shows is a block of the look's own."

A design is not a block of its own (the [Block] is the contract's, so staff see one "Hero" whichever look draws it), and a look has one design per contract. A design outside every look is the default design: what a look with none of its own draws, and what an app with no looks draws.

An app may design a PACKAGE's contract in its own source, as a class implements a library's interface — no kit of its own needed:

using Osysharp.Site.Looks;

component StoreHero(string title, Markdown words = "", Picture photo = "", Picture second = "", string button = "Shop now",
                    PageLink link = "/", Color ground = Colors.Surface) : Osysharp.Site.Looks.Hero {
  render { Stack(p: 12, bg: ground) { Text(title, fontSize: FontSize.Title); Markdown(words); } }
}

The app's design is drawn wherever the contract is placed — by the app's pages, and by the package's own pages and regions too. It takes the place of the package's design for the same look: an app's design outside every look replaces the kit's default design, as an app's component shadows a kit's of the same name.

What is a look? — look Name { … }#

A look is everything its NAMESPACE declares: its designs, its regions, its own blocks and the theme it names. So a look lives in a namespace of its own — a look kit's namespace Osysharp.Shop.Looks.Atelier;.

settingwhat it is
Label = "…"what staff call it on the desk (its name if left out)
Hint = "…"the line under it (the first sentence of its doc comment if left out)
Theme = Xits theme — served only while the site wears it, under :root[data-look="atelier"], so five looks' palettes live in one app without one taking the others' names (theme tokens); its dark column and its variants (a Christmas palette of this look) come with it

A region the look declares may share its name with another look's: it is ONE arrangement, drawn by each look in its own design, and what it allows and its default arrangement are the look's. A [Block] declared in the look's namespace is a look-only block — drawn only while the site wears that look.

An app with more than one look names the one the site wears until staff choose another — app.Look = Atelier; — and is refused without it. A version's look is SiteVersion.Look; the one showing to this browser is Visitor.Look, which the server stamps on the first paint (data-look on the page) and Theme.Look(name) moves (the PageBuilder kit's SiteLook() does, when a version switches or a member of staff previews another).

app Florist {
  use Osysharp.Ui;
  use Osysharp.Blocks;
  model "model/**/*.osy";
}

using Osysharp.Blocks;

app.Look = Meadow;

[Principal] public entity Member { [Required, Unique, MaxLength(200)] string Email; }

/// This week's flowers, large, with a line under them.
[Block("Bouquet")]
public abstract component Bouquet([Label("Headline"), MaxLength(80)] string title, [Label("Words under it")] Markdown words = "");

/// The front page, in whichever look the site wears.
public abstract component FrontPage();

[Route("/")]
[AllowAnonymous]
component Front() { render { FrontPage(); } }
namespace Florist.Looks.Meadow;

using Osysharp.Blocks;

/// Pale paper and a green that grows.
look Meadow { Label = "Meadow"; Theme = MeadowTheme; }

theme MeadowTheme { Colors { Bg = "#FFFDF5"; Leaf = "#3A7D44"; } }

component MeadowFront() : FrontPage {
  render { Region("front", allow: [Bouquet, Pressed]) { Bouquet(title: "Flowers cut this morning"); Pressed(); } }
}

component MeadowBouquet(string title, Markdown words = "") : Bouquet {
  render { Stack(gap: 2, p: 8, label: "Bouquet") { Text(title, fontSize: FontSize.Title, color: Colors.Leaf); Markdown(words); } }
}

/// A flower pressed between two pages — Meadow's own block.
[Block("Pressed flower")]
component Pressed([Label("Words"), MaxLength(80)] string words = "A daisy from June") {
  render { Text("✿ " + words, p: 4, color: Colors.Leaf); }
}
namespace Florist.Looks.Night;

using Osysharp.Blocks;

look Night { Label = "Night"; Theme = NightTheme; }

theme NightTheme { Colors { Bg = "#101014"; Leaf = "#9FD3A8"; } }

component NightFront() : FrontPage {
  render { Box(bg: Colors.Bg, minH: "100vh") { Region("front", allow: [Bouquet]) { Bouquet(title: "Evening bouquets"); } } }
}

component NightBouquet(string title, Markdown words = "") : Bouquet {
  render { Text(title, p: 6, fontSize: FontSize.Title, color: Colors.Leaf); }
}

Staff who arranged Meadow's front — their own headline on the bouquet — and switch the version to Night see the same headline in Night's design. The pressed flower has no design in Night: it stays in the arrangement and is not drawn while the version wears Night, and comes back with Meadow.

Switching a version's look — and what will not come along#

At the desk ([[ui-pages-kit#looks]]) each version names its look. Before a switch, the desk lists everything the new look has no design for — a look-only block, a type the look does not draw, a region it has no place for — and switches only when staff say so. Nothing is dropped: the arrangement keeps it, and a later switch back draws it again.

The mechanism is Osysharp.Blocks': SetLook(version, look) (a look the app has, or "" for the app's), LookLeavesBehind (version, look) (one line per thing that will not come along), SiteOffers().Looks and .DefaultLook, and BlockSpec.EveryLook/.Looks (which looks draw a block).

How do looks ship as kits? — the three layers#

A look is normally a KIT, so a store installs designs like any package and a marketplace can offer them. Two contracts:

  • Osysharp.Site.Looks (kits/site-looks) — the GENERAL site contract, for any consumer-facing site: Hero, PictureWithWords, Quote, SignUp, Banner, PhotoGallery, and around every page Announcement, FooterWords, FooterLink, OpeningHours, Address, FooterSignUp.
  • Osysharp.Shop.Looks (inside the shop kit, its types being the shop's data) — the SHOP's: the block types ProductRow and CategoryTiles, and the parts of a store — ShopFrame (with the page inside it), FrontPage, ShopSection, ListingPage, ProductGrid, ProductPage, NotFound. The shop kit's plain design of each block type — the shop's two and every general one — is the default design. The parts have none: a store that names them draws them in a look, so a shop look draws all seven (one it skips is drawn by the store's default look's design).

A look kit draws both (kits/look-atelier … kits/look-studio, the five designs of the Vall fashion store, and kits/look-rail … kits/look-loud, the five of the Second Round second-hand shop), and carries its fonts in its own osyrin.lock — a store that installs it ships them. A look kit carries no data and no logic, and needs no egress and no secret: it reads what a store's page reads, as the visitor.

So there are three layers, and each answers one question: the site contract says what any site has, a category contract (the shop's, today) says what that kind of site has on top, and a look says how all of it looks — it draws both contracts below it. Building one from an empty kit — the manifest, the theme, a design of each part, the fonts, a block of its own, seeing it in a store, and where a café's contract would sit beside the shop's — is Building a look — from an empty kit to a design a store can wear.

What the compiler refuses#

  • a design whose parameters are not the contract's — a name, a type, one too many or too few, named;
  • : X where X is not a contract; two designs of one contract in one look, or two outside every look;
  • [Block] or [Route] on a design; an abstract component with a body, or implementing something itself;
  • a look outside a namespace of its own, two looks of one name, a look naming a theme nobody declares, two looks sharing a theme, a setting other than Label/Hint/Theme;
  • an app with several looks and no app.Look, and an app.Look naming a look the app does not have;
  • an app's own design of a package's contract (see the warning above);
  • two regions of one name within one look.

See also#

Related

Building a look — from an empty kit to a design a store can wear

A look is a kit that draws two contracts: the general site contract (`Osysharp.Site.Looks` — a hero, a quote, the…

Blocks and regions — pages the staff arrange

A `[Block]` is a component staff can place on a page: its parameters are the fields they fill in, and each parameter's…

The site's frame — a menu, a strip and a footer staff change

The frame around every page — the menu, the strip across the top, the footer — can be the staff's to change, like a…

The PageBuilder kit — the desk staff arrange the site at

`Osysharp.PageBuilder` is the desk where the people who run a site arrange its [[ui-blocks|regions]]: add, move, fill…

theme tokens

A `theme` block names your app's design tokens — colors, spacing, radii, and more — as reusable values. A token can…

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…