Add landing page for kjol, documentation
This commit is contained in:
@@ -48,12 +48,12 @@ func tableColumns() []ui.AutoTableColumn {
|
||||
Key: "name", DisplayName: "Name", Sortable: true, SortIdentifier: "Name",
|
||||
CSV: true, CSVValue: func(r any) string { return emp(r).Name },
|
||||
// No Toggleable: the name is what identifies a row, so it cannot be hidden.
|
||||
Cell: func(r any) *VNode { return ui.AutoTableTdLeft("text-neutral-800", Text(emp(r).Name)) },
|
||||
Cell: func(r any) *VNode { return ui.AutoTableTdLeft("text-ink", Text(emp(r).Name)) },
|
||||
},
|
||||
{
|
||||
Key: "email", DisplayName: "Email", Sortable: true, SortIdentifier: "Email",
|
||||
Toggleable: true, CSV: true, CSVValue: func(r any) string { return emp(r).Email },
|
||||
Cell: func(r any) *VNode { return ui.AutoTableTdLeft("text-neutral-500", Text(emp(r).Email)) },
|
||||
Cell: func(r any) *VNode { return ui.AutoTableTdLeft("text-ink-muted", Text(emp(r).Email)) },
|
||||
},
|
||||
{
|
||||
Key: "team", DisplayName: "Team", Sortable: true, SortIdentifier: "Team",
|
||||
@@ -162,7 +162,7 @@ func newEmployeeTable(highlight *Signal[string]) *ui.AutoTableState {
|
||||
Accordion: true,
|
||||
RowKey: func(r any) string { return emp(r).Email },
|
||||
AccordionContent: func(r any) *VNode {
|
||||
return P(Attr("class", "px-4 py-2 text-sm text-neutral-600"), Text(emp(r).Note))
|
||||
return P(Attr("class", "px-4 py-2 text-sm text-ink-soft"), Text(emp(r).Note))
|
||||
},
|
||||
|
||||
Columns: ui.AutoTableColumnOptions{
|
||||
@@ -202,19 +202,37 @@ func TablePage(d Deps) func() *VNode {
|
||||
}
|
||||
return ui.AutoTablePDFHeader{
|
||||
Title: "Employees",
|
||||
Subtitle: "Exported from the gowasm example",
|
||||
Subtitle: "Exported from the Kjol Web example",
|
||||
ShowDate: true,
|
||||
Orientation: orientation,
|
||||
}
|
||||
}
|
||||
|
||||
return func() *VNode {
|
||||
return Div(Attr("class", "space-y-6"),
|
||||
Div(
|
||||
H2(Attr("class", "text-2xl font-semibold tracking-tight text-text-heading"), Text("AutoTable")),
|
||||
P(Attr("class", "mt-1 text-neutral-500"),
|
||||
Text("Filtering, sorting, pagination, expandable rows and column management — all in Go. "+
|
||||
"Drag a header to reorder, drag its right edge to resize; both persist across reloads.")),
|
||||
return docPage("Components", "AutoTable",
|
||||
"A table that filters, sorts, pages, reorders, resizes, computes and exports — configured with "+
|
||||
"a column list and a slice of rows. Everything a user changes about it is theirs and persists; "+
|
||||
"everything it exports is what they filtered, not what happened to be on screen.",
|
||||
|
||||
docSection("defining", "Defining one",
|
||||
prose("A column says how to read a field, how to sort it, and how to render it. The state object "+
|
||||
"is a CONTROLLER: build it once, alongside your signals — never inside the render, which "+
|
||||
"would hand it fresh refs and a fresh idea of which page it was on every frame."),
|
||||
code("app/table.go", tableSnippet),
|
||||
note("The server renders a skeleton, on purpose",
|
||||
"The layout — column order, widths, what is hidden, the calculated columns — lives in the "+
|
||||
"browser's localStorage, which the server cannot read. So the server ships a skeleton "+
|
||||
"rather than the DEFAULT table: a user who had reordered their columns would otherwise "+
|
||||
"watch them rearrange themselves the moment the WebAssembly booted."),
|
||||
),
|
||||
|
||||
docSection("try-it", "Try it",
|
||||
prose("Search matches name or email. Sort by Salary and it parses the currency, so $980 sorts "+
|
||||
"below $1,200.50. Unhide Rank and sort that: \"Item 2\" comes before \"Item 10\", because "+
|
||||
"numbers inside text are compared as numbers. Drag a header to reorder it, drag its right "+
|
||||
"edge to resize — reload the page and both are still where you left them."),
|
||||
prose("Filter it, then export. You get every matching row across every page, in the column order "+
|
||||
"you dragged them into, with the calculated columns computed per row."),
|
||||
),
|
||||
|
||||
table.Render(
|
||||
@@ -270,7 +288,7 @@ func TablePage(d Deps) func() *VNode {
|
||||
|
||||
// Highlight + auto-page-jump: Radia is on page 3 by default, and the table
|
||||
// pages itself to wherever she actually is once filters and sorting move her.
|
||||
row("flex flex-wrap items-center gap-2",
|
||||
row("mt-4 flex flex-wrap items-center gap-2",
|
||||
ui.Button(ui.ButtonProps{Color: ui.ButtonLightNeutral, Small: true,
|
||||
Text: "Find Radia Perlman",
|
||||
OnClick: func() { highlight.Set("radia@example.com") }}),
|
||||
@@ -279,12 +297,59 @@ func TablePage(d Deps) func() *VNode {
|
||||
OnClick: func() { highlight.Set("") }}),
|
||||
),
|
||||
|
||||
ui.Alert(ui.AlertBlue, "What to try",
|
||||
Text("Search (it matches name OR email); pick a status; select several teams. Sort by Salary — "+
|
||||
"it parses the currency, so $980 sorts below $1,200.50. Unhide Rank and sort it: 'Item 2' "+
|
||||
"comes before 'Item 10'. Click a row to expand it. Drag a header to reorder, drag its right "+
|
||||
"edge to resize — both survive a reload. Filter the table, then export: you get every "+
|
||||
"matching row, not just this page. 'Find Radia' jumps to whichever page she is on.")),
|
||||
docSection("calculated", "Calculated columns",
|
||||
prose("The toolbar's calculator builds new columns at runtime, in two modes. Basic picks a "+
|
||||
"function and the columns it combines ACROSS each row — sum of Salary and Bonus, per "+
|
||||
"person. Advanced writes a formula, with insert menus for columns, functions and constants: "+
|
||||
"([Salary] + [Bonus]) * 12."),
|
||||
prose("A summary row is the other axis: it aggregates ONE column DOWN the filtered rows and "+
|
||||
"prints the result in the footer. Confusing the two is the classic bug here — a column that "+
|
||||
"aggregates down shows every row the same number, and it looks plausible enough to ship."),
|
||||
codeLang("formulas", "syntax", formulaSnippet),
|
||||
note("Compiled as you type",
|
||||
"The formula is parsed and evaluated against the real first row while you write it, so a "+
|
||||
"typo shows up as an error under the box — not as a column of dashes discovered later."),
|
||||
),
|
||||
|
||||
docSection("export", "Export",
|
||||
prose("CSV and PDF are written in Go, standard library only — the PDF writer builds its own "+
|
||||
"xref table and embeds Helvetica metrics. Export takes the FILTERED rows, the VISIBLE "+
|
||||
"columns, in the user's order, including whatever they calculated."),
|
||||
apiTable(
|
||||
apiRow{"NewAutoTableState", "Build the controller: the columns, and where to persist the layout."},
|
||||
apiRow{".SetRows", "Hand it the data. It filters, sorts and pages from there."},
|
||||
apiRow{".RestoreLayout", "Read the saved layout and reveal the table over its skeleton. Call it once, on the client."},
|
||||
apiRow{".FilteredRows / .ExportColumns", "What the user selected, and what they can see — the inputs to any export."},
|
||||
apiRow{"ExportCSV / ExportPDF", "Write the bytes. DownloadCSV / DownloadPDF / PrintPDF do it and hand them to the browser."},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const tableSnippet = `// A controller: built ONCE, next to your signals — never inside the render.
|
||||
table := ui.NewAutoTableState([]ui.AutoTableColumn{
|
||||
{DisplayName: "Name", SortIdentifier: "Name", Sortable: true,
|
||||
Cell: func(r any) *VNode { return Text(r.(Employee).Name) }},
|
||||
{DisplayName: "Salary", SortIdentifier: "Salary", Sortable: true,
|
||||
SortType: ui.SortTypeNumeric, // parses the currency: $980 < $1,200.50
|
||||
Cell: func(r any) *VNode { return Text(money(r.(Employee).Salary)) }},
|
||||
{DisplayName: "Rank", HiddenByDefault: true},
|
||||
}, ui.AutoTableStateOptions{
|
||||
PerPage: 5,
|
||||
Columns: ui.AutoTableColumnOptions{
|
||||
StorageKey: "employees", // order, widths, visibility — the user's, and persisted
|
||||
},
|
||||
})
|
||||
|
||||
table.SetRows(employees())`
|
||||
|
||||
const formulaSnippet = `A COLUMN combines operands ACROSS one row:
|
||||
|
||||
sum[Salary, Bonus] -> 1200.50 + 150.00 = 1350.50 (per person)
|
||||
([Salary] + [Bonus]) * 12 -> the annualised figure
|
||||
SUM({Salary:1:ROW()}) -> a running total, down the rows
|
||||
|
||||
A SUMMARY ROW aggregates ONE column DOWN the filtered rows:
|
||||
|
||||
avg[Salary] -> one number, printed in the footer`
|
||||
|
||||
Reference in New Issue
Block a user