Rask
Rask
v0.23.1-alpha.0.440

Components

Kanban

Cards arranged in columns, one column per stage of a workflow. It draws the board and moves nothing, as Flux's does: adding or moving a card is the page changing its own lists.

Drawn and behaving as Flux UI's kanban does, measured against its live docs.

Kanban

A column per stage, each with its name, a count and its cards.

KanbanExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class KanbanExample : Component
{
    private static readonly (string Stage, string[] Cards)[] Board =
    [
        ("Planned", ["Update privacy policy in app", "Search bar suggestions broken", "Improve loading spinner visuals", "Date picker keyboard input fails"]),
        ("In Progress", ["Mobile responsive improvements", "Data table sorting broken", "API error codes inconsistent"]),
        ("In review", ["Button double-click issue", "Crash on large file upload"]),
    ];

    // A column does not shrink, so a board wider than its place scrolls in a box of its own.
    protected override Component? Render() =>
        Div.Class("overflow-x-auto pb-2")[
            Ui.Kanban[
                Board.Select(column => Ui.KanbanColumn.Key(column.Stage)[
                    Ui.KanbanColumnHeader.Heading(column.Stage).Count(column.Cards.Length),
                    Ui.KanbanColumnCards[column.Cards.Select(card => Ui.KanbanCard.Key(card).Heading(card))]
                ])
            ]
        ];
}
Live result
Planned
4
Update privacy policy in app
Search bar suggestions broken
Improve loading spinner visuals
Date picker keyboard input fails
In Progress
3
Mobile responsive improvements
Data table sorting broken
API error codes inconsistent
In review
2
Button double-click issue
Crash on large file upload

Column actions

Buttons at the end of the heading's row. The plus adds a card to the page's list.

KanbanActionsExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class KanbanActionsExample : Component
{
    private readonly List<string> _planned = ["Search bar suggestions broken", "Improve loading spinner visuals"];

    protected override Component? Render() =>
        Ui.KanbanColumn[
            Ui.KanbanColumnHeader.Heading("Planned").Count(_planned.Count).Actions([
                Ui.Button.Subtle.Sm.Icon(Ui.IconName.EllipsisHorizontal).AriaLabel("Column options"),
                Ui.Button.Subtle.Sm.Icon(Ui.IconName.Plus).AriaLabel("New card").OnClick(() => _planned.Add($"Card {_planned.Count + 1}"))
            ]),
            Ui.KanbanColumnCards[_planned.Select(card => Ui.KanbanCard.Key(card).Heading(card))]
        ];
}
Live result
Planned
2
Search bar suggestions broken
Improve loading spinner visuals

Subheading

KanbanSubheadingExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class KanbanSubheadingExample : Component
{
    protected override Component? Render() =>
        Ui.KanbanColumn[
            Ui.KanbanColumnHeader.Heading("Backlog").Subheading("Ideas and suggestions"),
            Ui.KanbanColumnCards[
                Ui.KanbanCard.Heading("Dark mode for the invoice PDF"),
                Ui.KanbanCard.Heading("Keyboard shortcuts cheat sheet")
            ]
        ];
}
Live result
Backlog
Ideas and suggestions
Dark mode for the invoice PDF
Keyboard shortcuts cheat sheet

Column footer

A form under the cards that adds one, and the count follows.

KanbanFooterExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class KanbanFooterExample : Component
{
    private readonly List<string> _planned = ["Search bar suggestions broken", "Improve loading spinner visuals"];
    private readonly NewCard _newCard = new();

    protected override Component? Render() =>
        Ui.KanbanColumn[
            Ui.KanbanColumnHeader.Heading("Planned").Count(_planned.Count),
            Ui.KanbanColumnCards[_planned.Select(card => Ui.KanbanCard.Key(card).Heading(card))],
            Ui.KanbanColumnFooter[
                Form.Model(_newCard).OnSubmit(Add)[
                    Ui.KanbanCard[
                        Div.Class("flex items-center gap-1")[
                            Ui.Heading.Class("flex-1")[
                                Input.Bind(() => _newCard.Title).Class("w-full outline-none").Placeholder("New card...").AriaLabel("New card")
                            ],
                            Ui.Button.Filled.Sm.Type(Ui.ButtonType.Submit).Inset(Ui.Inset.Top | Ui.Inset.Bottom).Class("-me-1.5")["Add"]
                        ]
                    ]
                ],
                Ui.Button.Subtle.Sm.Icon(Ui.IconName.Plus).Align(Ui.Align.Start)
                    .OnClick(() => _planned.Add($"Card {_planned.Count + 1}"))["New card"]
            ]
        ];

    private void Add(NewCard card)
    {
        if (string.IsNullOrWhiteSpace(card.Title))
        {
            return;
        }

        _planned.Add(card.Title.Trim());
        card.Title = "";
    }

    private sealed class NewCard
    {
        public string Title { get; set; } = "";
    }
}
Live result
Planned
2
Search bar suggestions broken
Improve loading spinner visuals

Cards as buttons

As(Ui.KanbanCardAs.Button) makes the whole card one to press, with badges above its heading or an icon under it.

KanbanCardButtonsExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class KanbanCardButtonsExample : Component
{
    private string? _opened;

    protected override Component? Render() =>
        Div.Class("space-y-3")[
            Ui.KanbanColumn[
                Ui.KanbanColumnHeader.Heading("Planned"),
                Ui.KanbanColumnCards[
                    Card("plain", "Update privacy policy in app"),
                    Card("header", "Search bar suggestions broken").Header(
                        Div.Class("flex gap-2")[
                            Ui.Badge.Sm.Color(Ui.Color.Blue)["UI"],
                            Ui.Badge.Sm.Color(Ui.Color.Green)["Backend"],
                            Ui.Badge.Sm.Color(Ui.Color.Red)["Bug"]
                        ]),
                    Card("footer", "Improve loading spinner visuals").Footer([
                        Ui.Icon.Name(Ui.IconName.Bars3BottomLeft).Variant(Ui.IconVariant.Micro).Class("text-zinc-400"),
                        Ui.Text.Inline()["3 people"]
                    ])
                ]
            ],
            Ui.Text.Data("testid", "ui-kanban-opened")[_opened is null ? "No card opened yet." : $"Opened: {_opened}"]
        ];

    private UiKanbanCard Card(string key, string title) =>
        Ui.KanbanCard.Key(key).As(Ui.KanbanCardAs.Button).Heading(title).OnClick(() => { _opened = title; });
}
Live result
Planned

No card opened yet.

Behaviour

A card is something to read

A card is a <div> until .As(Ui.KanbanCardAs.Button) makes the whole card a <button>: focused with Tab, pressed with Enter or Space, lighter under the pointer. OnClick is what it does. Header goes above the heading (badges), Footer under it (an icon, avatars), and children replace the heading.

It moves nothing

Exactly as Flux's: no card is draggable, no key reorders one, and there is no drop event. Adding or moving a card is your page changing its own lists and rendering again; for dragging, DragDrop is the framework's primitive.

Fitting a board in

A column does not shrink, so a board wider than its place needs a box that scrolls: Div.Class("overflow-x-auto")[Ui.Kanban[…]].

Flux's badge prop on the column header is not carried: no example on Flux's page draws it, so there is nothing to measure it against. A Ui.Badge of your own goes in as a child, beside the heading you write there.

Reference

Ui.Kanban

flux:kanban

The row the columns stand in, 16px apart. It draws a board and moves nothing.

No steps of its own: what goes inside it is its content.

It is one HTML element, so Id, Class, Style, Data, Aria and the events are its steps too.

Ui.KanbanColumn

flux:kanban.column

One stage: a 320px tinted panel as tall as what is in it.

No steps of its own: what goes inside it is its content.

It is one HTML element, so Id, Class, Style, Data, Aria and the events are its steps too.

Ui.KanbanColumnHeader

flux:kanban.column.header

The stage's name and what goes beside it. Children replace the heading and count.

Step
Takes
What it does
.Heading(…)stringWhat the column is called.
.Subheading(…)stringA second line under the heading.
.Count(…)intA number beside the heading: how many cards the column holds.
.Actions(…)ComponentButtons or a menu at the end of the heading's row.

It is one HTML element, so Id, Class, Style, Data, Aria and the events are its steps too.

Ui.KanbanColumnCards

flux:kanban.column.cards

The column's cards, one under the other.

No steps of its own: what goes inside it is its content.

It is one HTML element, so Id, Class, Style, Data, Aria and the events are its steps too.

Ui.KanbanColumnFooter

flux:kanban.column.footer

What sits under the cards: a New card button, a form.

No steps of its own: what goes inside it is its content.

It is one HTML element, so Id, Class, Style, Data, Aria and the events are its steps too.

Ui.KanbanCard

flux:kanban.card

A card. Children replace the heading.

Step
Takes
What it does
.Heading(…)stringThe card's title.
.As(…)
Ui.KanbanCardAs
Div
Button

Each is a step of its own too, unless an HTML tag has its name.

The element it is: Button makes the whole card one to press. A div when unset.
.Header(…)ComponentWhat sits above the heading: badges, tags.
.Footer(…)ComponentWhat sits under the heading: an icon, avatars.

It is one HTML element, so Id, Class, Style, Data, Aria and the events are its steps too.