Lesson 6 of 14

Anatomy: parts, kinds and slots

What a part is, how parts nest by path, when a part is present, and what a slot promises.

#The idea

A part says what it is, where it sits, whether it is there, and what it holds. Those four facts are the whole of anatomy, and every one of them is a statement a reader can resolve without asking anyone.

#What this lesson adds

An icon part before the label, and a prop to feed it:

milda
icon : string "A glyph shown before the label, by name."
milda
part icon: icon {
  content bind icon
  ink on-accent
  when variant = Secondary {
    ink text
  }
  when variant = Ghost {
    ink accent
  }
}

#What to notice

  • A part's name is a path. icon and label are children of the root because their names have no dotted prefix. Lesson 7 writes part Header.Title: the child Title of the part Header. The prefix is the parent; the path is the only place nesting is written down.
  • Kinds are a short closed list. container, item, text, control, input, output, icon, content, fragment, table, row, cell, instance, foreign. An icon part is an icon on every platform; what draws it is the target's decision.
  • An icon's content is the glyph's name. content bind icon takes it from the prop, and the part renders only while the prop is given. A part can also be present only under a condition of its own, written present when <predicate>; lesson 8 uses it for an error hint. Present means rendered, not visible: an absent part is not in the tree at all.
  • Where a part came from is recorded. A part the archetype declares has the default origin and is printed without one; a part you add is origin author. The editor uses this to know what it may offer to remove.
  • Text is , never . is the paint facet for a type style; a word that meant two things depending on its argument would read fine and parse wrong.
  • A slot has an arity and a contract. slot of content default on the label means: exactly one fill, free content, and this is where positional children go. and are the other arities; of Badge would restrict the fill to one component.

#What the generator makes of it

The icon became {icon && <Icon name={icon} />} before the label span, drawing from a generated Icon module that lesson 12 fills with real glyphs.

#Try it

Give the Button a part badge: instance after the label, then read lesson 7 to find out what to put in it. Or make the label slot and see what the panel says about an icon-only button.

Words in this lesson

  • What the part IS, in neutral terms.
  • A dotted path addresses something by name: a token (space.sm), a part (Header.Title), a context value.
  • The part exists only while the predicate holds - not hidden, absent.
  • Where the part came from: provenance.
  • The text the part shows: fixed, bound to a prop, or chosen by conditions.
  • The part is a hole the consumer fills: the label of a button, the items of a menu.

Open a word for its entry, or read them all in the reference.

#The file at the end of this lesson

milda
milda 0.10
prelude 0.0.2

meta { name "Acme" }

context-group ColorScheme { Light Dark }

context-group Variant { Primary Secondary Ghost }

foundations {
  layer Base {
    token violet/100 : color = hex(ede9fe)
    token violet/500 : color = hex(8b5cf6)
    token violet/600 : color = hex(7c3aed)
    token violet/700 : color = hex(6d28d9)
    token violet/900 : color = hex(4c1d95)
    token gray/50 : color = hex(f8fafc)
    token gray/200 : color = hex(e2e8f0)
    token gray/500 : color = hex(64748b)
    token gray/800 : color = hex(1e293b)
    token gray/900 : color = hex(0f172a)
    token green/500 : color = hex(22c55e)
    token amber/500 : color = hex(f59e0b)
    token white : color = hex(ffffff)
  }

  layer Semantic {
    token accent : color = alias by ColorScheme { default -> violet.600; Dark -> violet.500 }
    token accent-hover : color = alias by ColorScheme { default -> violet.700; Dark -> violet.600 }
    token accent-subtle : color = alias by ColorScheme { default -> violet.100; Dark -> violet.900 }
    token on-accent : color = alias by ColorScheme { default -> white }
    token surface : color = alias by ColorScheme { default -> white; Dark -> gray.900 }
    token text : color = alias by ColorScheme { default -> gray.900; Dark -> gray.50 }
    token text-muted : color = alias by ColorScheme { default -> gray.500 }
    token line : color = alias by ColorScheme { default -> gray.200; Dark -> gray.800 }
    token success : color = alias by ColorScheme { default -> green.500 }
    token warning : color = alias by ColorScheme { default -> amber.500 }

    token space/xs : spacing = 4px
    token space/sm : spacing = 8px
    token space/md : spacing = 12px
    token space/lg : spacing = 16px
    token space/xl : spacing = 24px

    token radius/sm : radius = 6px
    token radius/lg : radius = 12px
    token radius/pill : radius = pill

    token hairline : border = 1px

    token size/sm : font-size = 13px
    token size/md : font-size = 15px
    token size/lg : font-size = 18px
    token weight/medium : font-weight = 500
    token weight/bold : font-weight = 700
  }
}

component Button "The primary action, in three emphases." {
  archetype Button

  props {
    variant : context Variant = Primary "Which emphasis the button carries."
    disabled : boolean = false
    loading : boolean = false "Shows a spinner in place of the label."
    loadingLabel : string required when loading "Read out while the spinner replaces the label."
    label : string = "Get started"
    icon : string "A glyph shown before the label, by name."
  }

  events {
    click
  }

  part Button: container {
    emits { click on activate }
    bind state disabled disabled
    bind state loading loading
    layout { stack row; gap space.sm; align center; distribute center }
    fill accent
    ink on-accent
    border accent hairline
    corner radius.sm
    inset space.sm space.lg
    when variant = Secondary {
      fill surface
      ink text
      border line hairline
    }
    when variant = Ghost {
      fill surface
      ink accent
      border surface hairline
    }
    state hovered {
      fill accent-hover
      border accent-hover hairline
    }
    state hovered when variant = Secondary {
      fill accent-subtle
    }
    state hovered when variant = Ghost {
      fill accent-subtle
      border accent-subtle hairline
    }
    state focus-visible {
      ring accent
    }
    state disabled {
      opacity 50
    }
  }

  part icon: icon {
    content bind icon
    ink on-accent
    when variant = Secondary {
      ink text
    }
    when variant = Ghost {
      ink accent
    }
  }

  part label: text {
    slot of content default
    content bind label
    text size.md
    weight weight.medium
  }
}