diff --git a/pkg/x/m3table/options.go b/pkg/x/m3table/options.go new file mode 100644 index 0000000..4bf6d7e --- /dev/null +++ b/pkg/x/m3table/options.go @@ -0,0 +1,54 @@ +package m3table + +import ( + "github.com/zodimo/go-compose/compose/ui" + "github.com/zodimo/go-compose/compose/ui/unit" + "github.com/zodimo/go-compose/pkg/api" +) + +// Column defines the configuration for a single column in the table. +type Column struct { + // Header is an optional composable that defines the column header. + Header api.Composable + // Weight is the flex weight of the column. If greater than 0, the column + // scales proportionally based on its weight. + Weight int + // Width is the fixed width of the column. This is used if Weight is 0. + Width unit.Dp +} + +type TableOptions struct { + Modifier ui.Modifier + MinHeaderRowHeight unit.Dp + MinRowHeight unit.Dp +} + +type TableOption func(*TableOptions) + +func DefaultTableOptions() TableOptions { + return TableOptions{ + Modifier: ui.EmptyModifier, + MinHeaderRowHeight: 56, // Material 3 standard data table header row height + MinRowHeight: 52, // Material 3 standard data table row height + } +} + +func WithModifier(modifier ui.Modifier) TableOption { + return func(o *TableOptions) { + o.Modifier = modifier + } +} + +// WithMinHeaderRowHeight sets the minimum height for the header row. +func WithMinHeaderRowHeight(height unit.Dp) TableOption { + return func(o *TableOptions) { + o.MinHeaderRowHeight = height + } +} + +// WithMinRowHeight sets the minimum height for data rows. +func WithMinRowHeight(height unit.Dp) TableOption { + return func(o *TableOptions) { + o.MinRowHeight = height + } +} diff --git a/pkg/x/m3table/table.go b/pkg/x/m3table/table.go new file mode 100644 index 0000000..53b2f77 --- /dev/null +++ b/pkg/x/m3table/table.go @@ -0,0 +1,90 @@ +package m3table + +import ( + "github.com/zodimo/go-compose/compose/foundation/layout/box" + "github.com/zodimo/go-compose/compose/foundation/layout/column" + "github.com/zodimo/go-compose/compose/foundation/layout/row" + "github.com/zodimo/go-compose/compose/material3/divider" + "github.com/zodimo/go-compose/compose/ui" + "github.com/zodimo/go-compose/modifiers/size" + "github.com/zodimo/go-compose/modifiers/weight" + "github.com/zodimo/go-compose/pkg/api" +) + +// Table constructs a basic material3 table layout given a list of columns, +// the number of data rows, and a factory function to create each cell's content. +func Table( + columns []Column, + rowCount int, + cellContent func(row, col int) api.Composable, + options ...TableOption, +) api.Composable { + + opts := DefaultTableOptions() + for _, option := range options { + if option != nil { + option(&opts) + } + } + + return func(c api.Composer) api.Composer { + c.StartBlock("Table") + + hasHeaders := false + for _, col := range columns { + if col.Header != nil { + hasHeaders = true + break + } + } + + c.WithComposable(column.Column( + c.Sequence( + c.When(hasHeaders, func(c api.Composer) api.Composer { + return c.Sequence( + row.Row( + func(c api.Composer) api.Composer { + for i, col := range columns { + c.Key(i, wrapCell(col, col.Header)) + } + return c + }, + row.WithModifier(size.MinHeight(int(opts.MinHeaderRowHeight))), + ), + divider.Divider(), + )(c) + }), + c.Range(rowCount, func(r int) api.Composable { + return row.Row( + func(c api.Composer) api.Composer { + for cIdx, col := range columns { + c.Key(cIdx, wrapCell(col, cellContent(r, cIdx))) + } + return c + }, + row.WithModifier(size.MinHeight(int(opts.MinRowHeight))), + ) + }), + ), + column.WithModifier(opts.Modifier), + )) + + return c.EndBlock() + } +} + +// wrapCell wraps the provided content into a box with proper width modifiers. +func wrapCell(col Column, content api.Composable) api.Composable { + var mod ui.Modifier = ui.EmptyModifier + if col.Weight > 0 { + mod = mod.Then(weight.Weight(col.Weight)) + } else if col.Width > 0 { + mod = mod.Then(size.Width(int(col.Width))) + } + + if content == nil { + content = func(c api.Composer) api.Composer { return c } + } + + return box.Box(content, box.WithModifier(mod)) +} diff --git a/test-plan.txt b/test-plan.txt new file mode 100644 index 0000000..ed5adb1 --- /dev/null +++ b/test-plan.txt @@ -0,0 +1,15 @@ +Ah, the user asked "What about row height constraints ?" +In `Table`, we currently don't constrain the row height. +Usually a table row in Material 3 has a standard minimum height, or we can add `MinHeight` to `TableOptions` or `RowOptions`. +But `pkg/x/m3table/table.go` uses `row.Row`. We can apply a `MinHeight` modifier to the `row.Row` for both headers and cells. +Let's see if there's a standard Material 3 table row height. The Material 3 spec for Data tables says: +Row height: 52dp +Header row height: 56dp + +We can add `MinHeight` modifier to the row. +Wait, let's look at `modifiers/size/constructor.go` to see if we have `MinHeight`. +Yes, `MinHeight(minHeight int) ui.Modifier`. + +Let's modify `pkg/x/m3table/table.go` to add a default `MinHeight` to rows, or maybe pass it via `TableOptions`. +Since it's a basic table, maybe we can just hardcode or provide options. +Let's provide `MinRowHeight unit.Dp` and `MinHeaderRowHeight unit.Dp` in `TableOptions`.