Course 1
archived
Component documentation
SGUI / Development
Shared toolbar state, cards/list mode and criteria.
Loading examples…
Loading examples…
Loading examples…
No direct parents in the documented component catalog.
Course | Status | Score | Action | |
|---|---|---|---|---|
Course 1 | archived | 100 | ||
Course 2 | active | 97 | ||
Course 3 | active | 94 | ||
Course 4 | archived | 91 | ||
Course 5 | active | 88 | ||
Course 6 | active | 85 | ||
Course 7 | archived | 82 | ||
Course 8 | active | 79 | ||
Course 9 | active | 76 | ||
Course 10 | archived | 73 | ||
Course 11 | active | 70 | ||
Course 12 | active | 67 | ||
Course 13 | archived | 64 | ||
Course 14 | active | 61 | ||
Course 15 | active | 58 | ||
Course 16 | archived | 55 | ||
Course 17 | active | 52 | ||
Course 18 | active | 49 |
Page-size configuration restarts pagination; the row sorter uses one complete 25-row page. Select rows to replace Export all with Export selected and reveal Archive selected. Shift-click a row selector to check or uncheck the range from the last clicked selector. Each click becomes the next anchor; selections outside the range stay unchanged. Enable the row sorter to show drag handles on one complete page, with or without checkboxes. Reordering requires an unsorted, unfiltered list. Dragging a selected row moves the whole selection. Actions announce local requests; the host owns creation, exports and persistence.
Course | Status | Score | Action | |
|---|---|---|---|---|
Course 1 | archived | 100 | ||
Course 2 | active | 97 | ||
Course 3 | active | 94 | ||
Course 4 | archived | 91 | ||
Course 5 | active | 88 | ||
Course 6 | active | 85 | ||
Course 7 | archived | 82 | ||
Course 8 | active | 79 | ||
Course 9 | active | 76 | ||
Course 10 | archived | 73 | ||
Course 11 | active | 70 | ||
Course 12 | active | 67 | ||
Course 13 | archived | 64 | ||
Course 14 | active | 61 | ||
Course 15 | active | 58 | ||
Course 16 | archived | 55 | ||
Course 17 | active | 52 | ||
Course 18 | active | 49 |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { Button, AppDataGrid, AppDataGridShell, CourseCardFrame, createDataGridColumns, createActionMenuColumn, type AppDataGridColumn, type AppDataGridRowDragReorderParams } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridShellExample() {
type Course={id:string;name:string;status:string;score:number};
const [rows,setRows]=useState<Course[]>([{"id":"1","name":"Course 1","status":"archived","score":100},{"id":"2","name":"Course 2","status":"active","score":97},{"id":"3","name":"Course 3","status":"active","score":94},{"id":"4","name":"Course 4","status":"archived","score":91},{"id":"5","name":"Course 5","status":"active","score":88},{"id":"6","name":"Course 6","status":"active","score":85},{"id":"7","name":"Course 7","status":"archived","score":82},{"id":"8","name":"Course 8","status":"active","score":79},{"id":"9","name":"Course 9","status":"active","score":76},{"id":"10","name":"Course 10","status":"archived","score":73},{"id":"11","name":"Course 11","status":"active","score":70},{"id":"12","name":"Course 12","status":"active","score":67},{"id":"13","name":"Course 13","status":"archived","score":64},{"id":"14","name":"Course 14","status":"active","score":61},{"id":"15","name":"Course 15","status":"active","score":58},{"id":"16","name":"Course 16","status":"archived","score":55},{"id":"17","name":"Course 17","status":"active","score":52},{"id":"18","name":"Course 18","status":"active","score":49}]);
const [message,setMessage]=useState("");
const [search,setSearch]=useState("");
const [view,setView]=useState<'list'|'cards'>("list");
const [selectedRowIds,setSelectedRowIds]=useState<Set<string>>(new Set([]));
const defaultActions=<Stack role="group" aria-label="Course actions" direction="row" wrap gap={2}>
<Button density="compact" variant="outlined" tone="neutral" onPress={()=>setMessage('New course requested.')}>New course</Button>
<Button density="compact" variant="outlined" tone="neutral" onPress={()=>setMessage('Export requested for all courses.')}>Export all</Button>
</Stack>;
const selectedActions=<Stack role="group" aria-label="Course actions" direction="row" wrap gap={2}>
<Button density="compact" variant="outlined" tone="neutral" onPress={()=>setMessage('New course requested.')}>New course</Button>
<Button density="compact" variant="outlined" tone="neutral" onPress={()=>setMessage(`Export requested for ${selectedRowIds.size} selected course(s).`)}>Export selected</Button>
<Button density="compact" variant="outlined" tone="neutral" onPress={()=>setMessage(`Archive requested for ${selectedRowIds.size} selected course(s).`)}>Archive selected</Button>
</Stack>;
const columns:AppDataGridColumn<Course>[] = createDataGridColumns([{field:'name',headerName:'Course',flex:2,minWidth:180,locked:true,truncate:true},{field:'status',headerName:'Status',width:140,filterType:'enum'},{field:'score',headerName:'Score',width:120,filterType:'number'},createActionMenuColumn<Course>({getMenuActions:row=>[{id:'open',label:'Open course',onPress:()=>setMessage(`Opened ${row.name}`)}]})]);
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}><AppDataGridShell label={"Courses"} rows={rows} columns={columns} getRowLabel={row=>row.name} selection={{selectedRowIds,onSelectedRowIdsChange:setSelectedRowIds}} searchValue={search} onSearchChange={setSearch} pageSizeOptions={[25,50,100]} defaultPaginationModel={{page:0,pageSize:25}} hideFooter={false} filterFields={[{"id":"status","label":"Status","type":"enum","enumOptions":[{"id":"active","label":"Active"},{"id":"archived","label":"Archived"}]}]} loading={false} refreshing={false} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} showResetView={true} toolbar={{show:true,showViewModeToggle:true,showSearchButton:true,showColumnsButton:true,showSortButton:true,showFilterButton:true,leftContent:defaultActions,leftContentWhenSelected:selectedActions,showRefreshButton:true,onRefresh:()=>setMessage('Refresh requested'),filterFields:[{"id":"status","label":"Status","type":"enum","enumOptions":[{"id":"active","label":"Active"},{"id":"archived","label":"Archived"}]}]}} view={{mode:view,onModeChange:setView,cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="h5">{row.name}</Typography>} body={<Typography>{row.status}</Typography>} />}}} style={{height:"max-content",flex:"none"}}/>{message && <Typography role="status">{message}</Typography>}</Stack>
</ThemeScope>
</>
);
}
Both columns display the same long course name at equal widths. The first wraps onto multiple lines; the second stays on one line with an ellipsis. Activate a row to see the host callback result; your application supplies navigation.
Wrapped text | Truncated text | |
|---|---|---|
Foundations of accessible learning experiences and inclusive course design | Foundations of accessible learning experiences and inclusive course design | |
Practical workshop on building accessible activities and providing meaningful learner feedback | Practical workshop on building accessible activities and providing meaningful learner feedback | |
Leading collaborative projects across distributed learning teams | Leading collaborative projects across distributed learning teams |
'use client';
import { useState } from 'react';
import { AppDataGrid, AppDataGridShell, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function MultilineGridExample() {
type Row={id:string;name:string};
const rows:Row[]=[{"id":"foundations","name":"Foundations of accessible learning experiences and inclusive course design"},{"id":"practice","name":"Practical workshop on building accessible activities and providing meaningful learner feedback"},{"id":"leadership","name":"Leading collaborative projects across distributed learning teams"}];
const [message,setMessage]=useState("");
const columns:AppDataGridColumn<Row>[]=[
{field:'name',headerName:'Wrapped text',flex:1,minWidth:200,truncate:false,locked:true},
{field:'truncated',headerName:'Truncated text',getCellValue:row=>row.name,flex:1,minWidth:200,truncate:true},
];
return (
<>
<Stack gap={3}>
<AppDataGridShell label="Courses with wrapped text" rows={rows} columns={columns} getRowLabel={row=>row.name} onRowAction={row=>setMessage('Opened '+row.name)} hideFooter toolbar={{show:false}} style={{height:"max-content",flex:"none"}}/>
{message && <Typography role="status">{message}</Typography>}
</Stack>
</>
);
}
A fixed reference configuration with its own row interactions and host feedback.
Course | Status | |
|---|---|---|
Course 1 | archived | |
Course 2 | active | |
Course 3 | active | |
Course 4 | archived | |
Course 5 | active | |
Course 6 | active |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[{"id":"1","name":"Course 1","status":"archived","score":100},{"id":"2","name":"Course 2","status":"active","score":97},{"id":"3","name":"Course 3","status":"active","score":94},{"id":"4","name":"Course 4","status":"archived","score":91},{"id":"5","name":"Course 5","status":"active","score":88},{"id":"6","name":"Course 6","status":"active","score":85}];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="compact">
<Stack gap={3}>
<AppDataGridShell label={"List · compact"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={false} refreshing={false} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"list",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
A fixed reference configuration with its own row interactions and host feedback.
Course | Status | |
|---|---|---|
Course 1 | archived | |
Course 2 | active | |
Course 3 | active | |
Course 4 | archived | |
Course 5 | active | |
Course 6 | active |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[{"id":"1","name":"Course 1","status":"archived","score":100},{"id":"2","name":"Course 2","status":"active","score":97},{"id":"3","name":"Course 3","status":"active","score":94},{"id":"4","name":"Course 4","status":"archived","score":91},{"id":"5","name":"Course 5","status":"active","score":88},{"id":"6","name":"Course 6","status":"active","score":85}];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<AppDataGridShell label={"List · comfortable"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={false} refreshing={false} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"list",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
A fixed reference configuration with its own row interactions and host feedback.
archived
active
active
archived
active
active
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[{"id":"1","name":"Course 1","status":"archived","score":100},{"id":"2","name":"Course 2","status":"active","score":97},{"id":"3","name":"Course 3","status":"active","score":94},{"id":"4","name":"Course 4","status":"archived","score":91},{"id":"5","name":"Course 5","status":"active","score":88},{"id":"6","name":"Course 6","status":"active","score":85}];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="compact">
<Stack gap={3}>
<AppDataGridShell label={"Cards · compact"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={false} refreshing={false} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"cards",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
A fixed reference configuration with its own row interactions and host feedback.
archived
active
active
archived
active
active
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[{"id":"1","name":"Course 1","status":"archived","score":100},{"id":"2","name":"Course 2","status":"active","score":97},{"id":"3","name":"Course 3","status":"active","score":94},{"id":"4","name":"Course 4","status":"archived","score":91},{"id":"5","name":"Course 5","status":"active","score":88},{"id":"6","name":"Course 6","status":"active","score":85}];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<AppDataGridShell label={"Cards · comfortable"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={false} refreshing={false} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"cards",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
Initial loading: no rows have arrived yet.
Course | Status | |
|---|---|---|
Loading rows | ||
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<AppDataGridShell label={"Loading dataset · comfortable"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={true} refreshing={false} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"list",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
Existing rows remain in place while the toolbar refresh icon shows pending progress. The host owns request completion.
Course | Status | |
|---|---|---|
Course 1 | archived | |
Course 2 | active | |
Course 3 | active | |
Course 4 | archived | |
Course 5 | active | |
Course 6 | active |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[{"id":"1","name":"Course 1","status":"archived","score":100},{"id":"2","name":"Course 2","status":"active","score":97},{"id":"3","name":"Course 3","status":"active","score":94},{"id":"4","name":"Course 4","status":"archived","score":91},{"id":"5","name":"Course 5","status":"active","score":88},{"id":"6","name":"Course 6","status":"active","score":85}];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<AppDataGridShell label={"Refreshing dataset · comfortable"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={false} refreshing={true} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"list",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
A fixed reference configuration with its own row interactions and host feedback.
Course | Status | |
|---|---|---|
No rows available | ||
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<AppDataGridShell label={"Empty dataset · comfortable"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={false} refreshing={false} errorMessage={undefined} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"list",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
The toolbar shows the request failure and its retry action while existing rows remain in place. The host owns retry and request completion.
Course | Status | |
|---|---|---|
Course 1 | archived | |
Course 2 | active | |
Course 3 | active | |
Course 4 | archived | |
Course 5 | active | |
Course 6 | active |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { AppDataGrid, AppDataGridShell, CourseCardFrame, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { Stack, Typography, Link } from '@structured-growth/sg-ui/primitives';
export default function GridReferenceExample() {
type Course={id:string;name:string;status:string;score:number};
const rows:Course[]=[{"id":"1","name":"Course 1","status":"archived","score":100},{"id":"2","name":"Course 2","status":"active","score":97},{"id":"3","name":"Course 3","status":"active","score":94},{"id":"4","name":"Course 4","status":"archived","score":91},{"id":"5","name":"Course 5","status":"active","score":88},{"id":"6","name":"Course 6","status":"active","score":85}];
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"status","headerName":"Status","width":100}];
const [message,setMessage]=useState("No reference action yet");
return (
<>
<ThemeScope density="comfortable">
<Stack gap={3}>
<AppDataGridShell label={"Error dataset · comfortable"} rows={rows} columns={columns} getRowId={row=>row.id} getRowLabel={row=>row.name} defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} loading={false} refreshing={false} errorMessage={"Sample request failed."} onRetry={()=>setMessage('Retry requested')} onRowAction={row=>setMessage('Opened '+row.name)} toolbar={{showViewModeToggle:false}} view={{mode:"list",cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="subtitle1">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}}/>
</Stack>
</ThemeScope>
</>
);
}
Change Rows per page, then refresh this page: the selected size is restored using an isolated localStorage key, together with criteria, columns, widths and view mode. Selection, host rows and request state are never persisted. Reset view restores the declared page size 6, ascending Course sort, empty search/filters, visible ordered columns and list mode. Storage initialization is SSR-safe and unavailable storage falls back to local state.
'use client';
import { useState } from 'react';
import {AppDataGridShell,CourseCardFrame,createActionMenuColumn,parseFilterRuleValues,type AppDataGridColumn,type AppDataGridProps,type DataToolbarFilterField} from '@structured-growth/sg-ui/components';
import {Stack,Typography} from '@structured-growth/sg-ui/primitives';
export default function PersistedCourseViewExample() {
type Course={id:string;name:string;status:string};
const records:Course[]=[{"id":"1","name":"Course 01","status":"archived"},{"id":"2","name":"Course 02","status":"active"},{"id":"3","name":"Course 03","status":"active"},{"id":"4","name":"Course 04","status":"archived"},{"id":"5","name":"Course 05","status":"active"},{"id":"6","name":"Course 06","status":"active"},{"id":"7","name":"Course 07","status":"archived"},{"id":"8","name":"Course 08","status":"active"},{"id":"9","name":"Course 09","status":"active"},{"id":"10","name":"Course 10","status":"archived"},{"id":"11","name":"Course 11","status":"active"},{"id":"12","name":"Course 12","status":"active"},{"id":"13","name":"Course 13","status":"archived"},{"id":"14","name":"Course 14","status":"active"},{"id":"15","name":"Course 15","status":"active"},{"id":"16","name":"Course 16","status":"archived"},{"id":"17","name":"Course 17","status":"active"},{"id":"18","name":"Course 18","status":"active"}];
const fields:DataToolbarFilterField[]=[{"id":"status","label":"Status","type":"enum","enumOptions":[{"id":"active","label":"Active"},{"id":"archived","label":"Archived"}]}];
const [result,setResult]=useState("");
const columns:AppDataGridColumn<Course>[]=[{field:'name',headerName:'Course',minWidth:160,flex:1,locked:true},{field:'status',headerName:'Status',width:120},createActionMenuColumn<Course>({getMenuActions:row=>[{id:'open',label:'Open course',onPress:()=>setResult('Open requested for '+row.name)}]})];
return (
<>
<Stack gap={3}>
<AppDataGridShell label="Saved course view" rows={records} columns={columns} getRowLabel={row=>row.name} persistence={{key:'docs-course-view-v1'}} showResetView
defaultPaginationModel={{page:0,pageSize:6}} pageSizeOptions={[6,10]} defaultSearchValue="" defaultSortRules={[{field:'name',direction:'asc'}]} defaultFilterRules={[]}
defaultColumnVisibilityModel={{name:true,status:true,actions:true}} defaultColumnOrder={['name','status','actions']} defaultColumnWidths={{status:120}}
filterFields={fields} toolbar={{showRefreshButton:true,onRefresh:()=>setResult('Local course records refreshed.')}}
view={{defaultMode:'list',cards:{renderCard:row=><CourseCardFrame header={<Typography as="h3" variant="h5">{row.name}</Typography>} body={<Typography>{row.status}</Typography>}/>}}} style={{height:"max-content",flex:"none"}} />
<Typography role="status">{result}</Typography>
</Stack>
</>
);
}
One host criteria owner accepts the shell's combined page/search/sort/filter/selection transaction; criteria changes reset page zero before querying. A bounded local promise models server processing. Search fail for rejection/retry; quickly replace Course 01 with Course 02 to exercise late-response rejection. The freshness guard protects success, error and pending state after changes/unmount. Production hosts translate criteria into their server contract and may additionally abort transport work.
Course | Status | Action | |
|---|---|---|---|
Loading rows | |||
'use client';
import { useState } from 'react';
import {AppDataGridShell,CourseCardFrame,createActionMenuColumn,parseFilterRuleValues,type AppDataGridColumn,type AppDataGridProps,type DataToolbarFilterField} from '@structured-growth/sg-ui/components';
import {Stack,Typography} from '@structured-growth/sg-ui/primitives';
import {useEffect} from 'react';
export default function ServerCourseViewExample() {
type Course={id:string;name:string;status:string};
const records:Course[]=[{"id":"1","name":"Course 01","status":"archived"},{"id":"2","name":"Course 02","status":"active"},{"id":"3","name":"Course 03","status":"active"},{"id":"4","name":"Course 04","status":"archived"},{"id":"5","name":"Course 05","status":"active"},{"id":"6","name":"Course 06","status":"active"},{"id":"7","name":"Course 07","status":"archived"},{"id":"8","name":"Course 08","status":"active"},{"id":"9","name":"Course 09","status":"active"},{"id":"10","name":"Course 10","status":"archived"},{"id":"11","name":"Course 11","status":"active"},{"id":"12","name":"Course 12","status":"active"},{"id":"13","name":"Course 13","status":"archived"},{"id":"14","name":"Course 14","status":"active"},{"id":"15","name":"Course 15","status":"active"},{"id":"16","name":"Course 16","status":"archived"},{"id":"17","name":"Course 17","status":"active"},{"id":"18","name":"Course 18","status":"active"}];
const fields:DataToolbarFilterField[]=[{"id":"status","label":"Status","type":"enum","enumOptions":[{"id":"active","label":"Active"},{"id":"archived","label":"Archived"}]}];
type Criteria=Parameters<NonNullable<AppDataGridProps<Course>['onStateChange']>>[0];
const [criteria,setCriteria]=useState<Criteria>({paginationModel:{"page":0,"pageSize":6},searchValue:"",sortRules:[],filterRules:[],selectedRowIds:new Set([])});
const [response,setResponse]=useState<{rows:Course[];total:number}>({rows:[],total:0});
const [loading,setLoading]=useState(true);const [error,setError]=useState<string>();const [retry,setRetry]=useState(0);const [result,setResult]=useState("");
const columns:AppDataGridColumn<Course>[]=[{field:'name',headerName:'Course',minWidth:160,flex:1,locked:true},{field:'status',headerName:'Status',width:120},createActionMenuColumn<Course>({getMenuActions:row=>[{id:'open',label:'Open course',onPress:()=>setResult('Open requested for '+row.name)}]})];
function queryCourses(criteria:Criteria,fail:boolean):Promise<{rows:Course[];total:number}> {
return new Promise((resolve,reject)=>setTimeout(()=>{
if(fail){reject(new Error('Sample course request failed. Retry to recover.'));return;}
let matches=records.filter(row=>row.name.toLowerCase().includes(criteria.searchValue.toLowerCase()));
for(const rule of criteria.filterRules)if(rule.field==='status'){const values=parseFilterRuleValues(rule.value);if(values.length)matches=matches.filter(row=>rule.operator==='is_not'?!values.includes(row.status):values.includes(row.status));}
for(const rule of [...criteria.sortRules].reverse())matches.sort((a,b)=>String(a[rule.field as 'name'|'status']).localeCompare(String(b[rule.field as 'name'|'status']))*(rule.direction==='desc'?-1:1));
const start=criteria.paginationModel.page*criteria.paginationModel.pageSize;
resolve({rows:matches.slice(start,start+criteria.paginationModel.pageSize),total:matches.length});
},criteria.searchValue==='Course 01'?650:200));
}
const requestKey=JSON.stringify({paginationModel:criteria.paginationModel,searchValue:criteria.searchValue,sortRules:criteria.sortRules,filterRules:criteria.filterRules});
useEffect(()=>{
let current=true;setLoading(true);setError(undefined);
queryCourses(criteria,criteria.searchValue.toLowerCase()==='fail'&&retry===0).then(next=>{if(current)setResponse(next);},reason=>{if(current)setError(reason instanceof Error?reason.message:'Course request failed.');}).finally(()=>{if(current)setLoading(false);});
return ()=>{current=false;};
},[requestKey,retry]);
return (
<>
<Stack gap={3}>
<AppDataGridShell label="Server course search" mode="server" rows={response.rows} rowCount={response.total} columns={columns} getRowLabel={row=>row.name} loading={loading} errorMessage={error} onRetry={()=>setRetry(value=>value+1)}
paginationModel={criteria.paginationModel} searchValue={criteria.searchValue} sortRules={criteria.sortRules} filterRules={criteria.filterRules} selection={{selectedRowIds:criteria.selectedRowIds}}
onStateChange={next=>{setCriteria(next);setRetry(0);}} pageSizeOptions={[6,10]} filterFields={fields} toolbar={{showRefreshButton:true,onRefresh:()=>setRetry(value=>value+1)}} showResetView defaultPaginationModel={{page:0,pageSize:6}} style={{height:"max-content",flex:"none"}} />
<Typography role="status">{result}</Typography>
</Stack>
</>
);
}
The host supplies course columns, localized date formatting and destination URLs. Details use the navigation adapter; Continue opens a new tab. This example does not persist course changes.
Course | Site | Due | Actions | |
|---|---|---|---|---|
Learning Studio | Due Oct 20 at 12:00 PM | |||
Learning Studio | Due date unavailable |
'use client';
import { AppDataGridShell, createActionMenuColumn, type AppDataGridColumn } from '@structured-growth/sg-ui/components';
import { formatDueDateLabel } from '@structured-growth/sg-ui/components/LearnerCourseCard';
export default function CourseCollectionExample() {
type Course={id:string;name:string;site:string;dueAt:string;detailsHref:string;launchHref:string};
const rows:Course[]=[{"id":"course-1","name":"Working with feedback","site":"Learning Studio","dueAt":"2026-10-20T12:00:00Z","detailsHref":"#course-details","launchHref":"#course-activity"},{"id":"course-2","name":"Planning a project","site":"Learning Studio","dueAt":"invalid","detailsHref":"#project-details","launchHref":"#project-activity"}];
const columns:AppDataGridColumn<Course>[]=[
{field:'name',headerName:'Course',flex:1,minWidth:230,cellType:'link',getLink:row=>({href:row.detailsHref})},
{field:'site',headerName:'Site',width:180},
{field:'dueAt',headerName:'Due',width:220,formatValue:value=>formatDueDateLabel(String(value),undefined,{locale:'en-US'})},
createActionMenuColumn<Course>({headerName:'Actions',getMenuActions:row=>[
{id:'details',label:'Details',href:row.detailsHref},
{id:'continue',label:'Continue',href:row.launchHref,target:'_blank',rel:'noopener noreferrer'},
]}),
];
return (
<>
<AppDataGridShell label="Assigned courses" rows={rows} columns={columns} getRowLabel={row=>row.name} pageSizeOptions={[6,10]} defaultPaginationModel={{page:0,pageSize:6}} style={{height:"max-content",flex:"none"}} />
</>
);
}
Drag a grabber to move a row. Handles also support keyboard dragging. All four rows stay on one unfiltered, unsorted page. This example accepts order changes locally; the host owns saving the order.
Course | Status | |
|---|---|---|
Plan the project | Ready | |
Research the topic | Ready | |
Draft the proposal | In progress | |
Review the proposal | Pending |
'use client';
import { useState } from 'react';
import {AppDataGridShell,type AppDataGridColumn,type AppDataGridRowDragReorderParams} from '@structured-growth/sg-ui/components';
export default function HandleOnlyReorderExample() {
type Course={id:string;name:string;status:string};
const [rows,setRows]=useState<Course[]>([{"id":"plan","name":"Plan the project","status":"Ready"},{"id":"research","name":"Research the topic","status":"Ready"},{"id":"draft","name":"Draft the proposal","status":"In progress"},{"id":"review","name":"Review the proposal","status":"Pending"}]);
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":200,"sortable":false,"locked":true},{"field":"status","headerName":"Status","width":140,"sortable":false}];
function reorder({sourceRowIds,targetRowId,position}:AppDataGridRowDragReorderParams<Course>) {
setRows(current=>{
const source=current.filter(row=>sourceRowIds.includes(row.id));
if(!source.length||sourceRowIds.includes(targetRowId))return current;
const next=current.filter(row=>!sourceRowIds.includes(row.id));
const target=next.findIndex(row=>row.id===targetRowId);
if(target<0)return current;
next.splice(target+(position==='after'?1:0),0,...source);
return next;
});
}
return (
<>
<AppDataGridShell label="Reorder courses without selection" rows={rows} columns={columns} getRowLabel={row=>row.name}
selection={false} rowDrag={{onReorder:reorder}} defaultPaginationModel={{page:0,pageSize:10}} pageSizeOptions={[10]}
toolbar={{showSortButton:false,showSearchButton:false,showFilterButton:false,showRefreshButton:false}} style={{height:"max-content",flex:"none"}} />
</>
);
}
Drag a grabber to move a row. Handles also support keyboard dragging. All four rows stay on one unfiltered, unsorted page. Drag a selected row to move all selected rows together in their current order; dragging an unselected row moves only that row. This example accepts order changes locally; the host owns saving the order.
Course | Status | |
|---|---|---|
Plan the project | Ready | |
Research the topic | Ready | |
Draft the proposal | In progress | |
Review the proposal | Pending |
'use client';
import { useState } from 'react';
import {AppDataGridShell,type AppDataGridColumn,type AppDataGridRowDragReorderParams} from '@structured-growth/sg-ui/components';
export default function SelectionAndReorderExample() {
type Course={id:string;name:string;status:string};
const [rows,setRows]=useState<Course[]>([{"id":"plan","name":"Plan the project","status":"Ready"},{"id":"research","name":"Research the topic","status":"Ready"},{"id":"draft","name":"Draft the proposal","status":"In progress"},{"id":"review","name":"Review the proposal","status":"Pending"}]);
const [selectedRowIds,setSelectedRowIds]=useState<Set<string>>(new Set([]));
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":200,"sortable":false,"locked":true},{"field":"status","headerName":"Status","width":140,"sortable":false}];
function reorder({sourceRowIds,targetRowId,position}:AppDataGridRowDragReorderParams<Course>) {
setRows(current=>{
const source=current.filter(row=>sourceRowIds.includes(row.id));
if(!source.length||sourceRowIds.includes(targetRowId))return current;
const next=current.filter(row=>!sourceRowIds.includes(row.id));
const target=next.findIndex(row=>row.id===targetRowId);
if(target<0)return current;
next.splice(target+(position==='after'?1:0),0,...source);
return next;
});
}
return (
<>
<AppDataGridShell label="Select and reorder courses" rows={rows} columns={columns} getRowLabel={row=>row.name}
selection={{selectedRowIds,onSelectedRowIdsChange:setSelectedRowIds}} rowDrag={{onReorder:reorder}} defaultPaginationModel={{page:0,pageSize:10}} pageSizeOptions={[10]}
toolbar={{showSortButton:false,showSearchButton:false,showFilterButton:false,showRefreshButton:false}} style={{height:"max-content",flex:"none"}} />
</>
);
}
Drag a handle onto a named target, or select rows and press its button. Tagging keeps rows in place; moving removes them from this Inbox view. Dragging a selected row transfers the selected rows on the current loaded page; dragging an unselected row transfers only that row. Sorting, search and pagination remain available. Keyboard: activate a handle with Enter, Tab to a destination, then Enter to drop; Escape cancels. Changes are local: the host owns permissions, saving folder/tag changes, errors and server-page refreshes.
Course | Tag | |
|---|---|---|
Course 1 | None | |
Course 2 | None | |
Course 3 | None |
'use client';
import { useState } from 'react';
import {AppDataGridShell,type AppDataGridColumn,type AppDataGridRowTransferParams} from '@structured-growth/sg-ui/components';
import {Stack,Typography} from '@structured-growth/sg-ui/primitives';
export default function TransferCourseRowsExample() {
type Course={id:string;name:string;tag:string};
const [rows,setRows]=useState<Course[]>([{"id":"1","name":"Course 1","tag":"None"},{"id":"2","name":"Course 2","tag":"None"},{"id":"3","name":"Course 3","tag":"None"},{"id":"4","name":"Course 4","tag":"None"}]);
const [selectedRowIds,setSelectedRowIds]=useState<Set<string>>(new Set([]));
const [result,setResult]=useState("");
const columns:AppDataGridColumn<Course>[]=[{"field":"name","headerName":"Course","flex":1,"minWidth":180,"locked":true},{"field":"tag","headerName":"Tag","width":140}];
const dropTargets=[{"id":"priority","label":"Tag as priority","operation":"copy"},{"id":"review","label":"Move to Review folder","operation":"move"}] as const;
function transfer({sourceRowIds,targetId}:AppDataGridRowTransferParams<Course>){
if(targetId==='priority'){
setRows(current=>current.map(row=>sourceRowIds.includes(row.id)?{...row,tag:'Priority'}:row));
setResult('Tagged '+sourceRowIds.length+' course(s) as Priority.');
} else if(targetId==='review'){
// This demo removes rows from the current view; the host saves folder membership.
setRows(current=>current.filter(row=>!sourceRowIds.includes(row.id)));
setSelectedRowIds(current=>new Set([...current].filter(id=>!sourceRowIds.includes(id))));
setResult('Moved '+sourceRowIds.length+' course(s) to Review.');
}
}
return (
<>
<Stack gap={3}>
<AppDataGridShell label="Inbox courses" rows={rows} columns={columns} getRowLabel={row=>row.name}
selection={{selectedRowIds,onSelectedRowIdsChange:setSelectedRowIds}}
rowDrag={{mode:'transfer',dropTargets,onDrop:transfer}}
defaultPaginationModel={{page:0,pageSize:3}} pageSizeOptions={[3]}
style={{height:'max-content',flex:'none'}}/>
{result && <Typography role="status">{result}</Typography>}
</Stack>
</>
);
}