feat(practice): add Needs Attention to unified compact layout
- Integrate "Needs Attention" section into unified compacting system - Single attention student flows with other compact sections - Multiple attention students get full sticky header - Add comprehensive Storybook stories for Needs Attention scenarios: - NeedsAttentionSingle, NeedsAttentionMultiple - NeedsAttentionWithCompactBuckets, NeedsAttentionFullThenCompact - NeedsAttentionRealistic, NeedsAttentionDarkMode - Update GroupedStudentsDemo to use unified renderItems array 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -461,67 +461,7 @@ export function PracticeClient({ initialPlayers, viewerId, userId }: PracticeCli
|
||||
{/* Shows for anyone with children, even if they're also a teacher */}
|
||||
<EntryPromptBanner />
|
||||
|
||||
{/* Needs Attention Section - uses same bucket styling as other sections */}
|
||||
{studentsNeedingAttention.length > 0 && (
|
||||
<div data-bucket="attention" data-component="needs-attention-bucket">
|
||||
{/* Bucket header - sticky below filter bar */}
|
||||
<h2
|
||||
data-element="bucket-header"
|
||||
className={css({
|
||||
position: 'sticky',
|
||||
top: '160px', // Nav (80px) + Filter bar (~80px)
|
||||
zIndex: Z_INDEX.STICKY_BUCKET_HEADER,
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '12px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.300',
|
||||
bg: isDark ? 'gray.900' : 'gray.50',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
})}
|
||||
>
|
||||
<span>⚠️</span>
|
||||
<span>Needs Attention</span>
|
||||
<span
|
||||
className={css({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minWidth: '20px',
|
||||
height: '20px',
|
||||
padding: '0 6px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: isDark ? 'orange.700' : 'orange.500',
|
||||
color: 'white',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'bold',
|
||||
})}
|
||||
>
|
||||
{studentsNeedingAttention.length}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{/* Student cards - intervention badges will show on each card */}
|
||||
<StudentSelector
|
||||
students={studentsNeedingAttention as StudentWithProgress[]}
|
||||
onSelectStudent={handleSelectStudent}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onObserveSession={handleObserveSession}
|
||||
title=""
|
||||
selectedIds={selectedIds}
|
||||
hideAddButton
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Grouped Student List */}
|
||||
{/* All Students - unified layout with compact sections flowing together */}
|
||||
{filteredGroupedStudents.length === 0 && studentsNeedingAttention.length === 0 ? (
|
||||
<ViewEmptyState
|
||||
currentView={currentView}
|
||||
@@ -542,204 +482,456 @@ export function PracticeClient({ initialPlayers, viewerId, userId }: PracticeCli
|
||||
gap: '24px',
|
||||
})}
|
||||
>
|
||||
{filteredGroupedStudents.map((bucket) => (
|
||||
<div key={bucket.bucket} data-bucket={bucket.bucket}>
|
||||
{/* Bucket header - sticky below filter bar */}
|
||||
<h2
|
||||
data-element="bucket-header"
|
||||
className={css({
|
||||
position: 'sticky',
|
||||
top: '160px', // Nav (80px) + Filter bar (~80px)
|
||||
zIndex: Z_INDEX.STICKY_BUCKET_HEADER,
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '12px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
bg: isDark ? 'gray.900' : 'gray.50',
|
||||
})}
|
||||
>
|
||||
{bucket.bucketName}
|
||||
</h2>
|
||||
{(() => {
|
||||
// Unified section type for both "Needs Attention" and regular buckets
|
||||
type Section =
|
||||
| {
|
||||
type: 'attention'
|
||||
students: typeof studentsNeedingAttention
|
||||
}
|
||||
| {
|
||||
type: 'bucket'
|
||||
bucket: (typeof filteredGroupedStudents)[0]
|
||||
}
|
||||
|
||||
{/* Categories within bucket - grouped for compact display */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
// Group consecutive compact categories (1 student, no attention placeholder)
|
||||
type RenderItem =
|
||||
| { type: 'compact-row'; categories: typeof bucket.categories }
|
||||
| { type: 'full'; category: (typeof bucket.categories)[0] }
|
||||
// Build list of all sections (attention first, then buckets)
|
||||
const allSections: Section[] = []
|
||||
if (studentsNeedingAttention.length > 0) {
|
||||
allSections.push({ type: 'attention', students: studentsNeedingAttention })
|
||||
}
|
||||
for (const bucket of filteredGroupedStudents) {
|
||||
allSections.push({ type: 'bucket', bucket })
|
||||
}
|
||||
|
||||
const items: RenderItem[] = []
|
||||
let compactBuffer: typeof bucket.categories = []
|
||||
// Helper to check if a category is compact (1 student, no attention placeholder)
|
||||
const isCategoryCompact = (
|
||||
bucket: (typeof filteredGroupedStudents)[0],
|
||||
cat: (typeof bucket.categories)[0]
|
||||
) => {
|
||||
const attentionCount =
|
||||
attentionCountsByBucket.get(bucket.bucket)?.get(cat.category) ?? 0
|
||||
return cat.students.length === 1 && attentionCount === 0
|
||||
}
|
||||
|
||||
for (const cat of bucket.categories) {
|
||||
const attentionCount =
|
||||
attentionCountsByBucket.get(bucket.bucket)?.get(cat.category) ?? 0
|
||||
const isCompact = cat.students.length === 1 && attentionCount === 0
|
||||
// Helper to check if entire bucket is compact
|
||||
const isBucketCompact = (bucket: (typeof filteredGroupedStudents)[0]) =>
|
||||
bucket.categories.every((cat) => isCategoryCompact(bucket, cat))
|
||||
|
||||
if (isCompact) {
|
||||
compactBuffer.push(cat)
|
||||
} else {
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
compactBuffer = []
|
||||
}
|
||||
items.push({ type: 'full', category: cat })
|
||||
}
|
||||
}
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
}
|
||||
// Helper to check if a section is compact
|
||||
const isSectionCompact = (section: Section) => {
|
||||
if (section.type === 'attention') {
|
||||
return section.students.length === 1
|
||||
}
|
||||
return isBucketCompact(section.bucket)
|
||||
}
|
||||
|
||||
return items.map((item, idx) => {
|
||||
if (item.type === 'compact-row') {
|
||||
// Render compact categories flowing together
|
||||
return (
|
||||
<div
|
||||
key={`compact-${idx}`}
|
||||
data-element="compact-category-row"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
alignItems: 'flex-start',
|
||||
})}
|
||||
>
|
||||
{item.categories.map((cat) => (
|
||||
<div
|
||||
key={cat.category ?? 'null'}
|
||||
data-category={cat.category ?? 'new'}
|
||||
data-compact="true"
|
||||
// Group consecutive compact sections
|
||||
type RenderItem =
|
||||
| { type: 'compact-sections'; sections: Section[] }
|
||||
| { type: 'full-section'; section: Section }
|
||||
|
||||
const renderItems: RenderItem[] = []
|
||||
let compactBuffer: Section[] = []
|
||||
|
||||
for (const section of allSections) {
|
||||
if (isSectionCompact(section)) {
|
||||
compactBuffer.push(section)
|
||||
} else {
|
||||
if (compactBuffer.length > 0) {
|
||||
renderItems.push({ type: 'compact-sections', sections: compactBuffer })
|
||||
compactBuffer = []
|
||||
}
|
||||
renderItems.push({ type: 'full-section', section })
|
||||
}
|
||||
}
|
||||
if (compactBuffer.length > 0) {
|
||||
renderItems.push({ type: 'compact-sections', sections: compactBuffer })
|
||||
}
|
||||
|
||||
return renderItems.map((item, itemIdx) => {
|
||||
if (item.type === 'compact-sections') {
|
||||
// Render compact sections flowing together
|
||||
return (
|
||||
<div
|
||||
key={`compact-sections-${itemIdx}`}
|
||||
data-element="compact-sections-row"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
alignItems: 'flex-start',
|
||||
})}
|
||||
>
|
||||
{item.sections.flatMap((section) => {
|
||||
if (section.type === 'attention') {
|
||||
// Compact attention section (1 student)
|
||||
return (
|
||||
<div
|
||||
key="attention"
|
||||
data-bucket="attention"
|
||||
data-compact="true"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2px',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
data-element="compact-label"
|
||||
className={css({
|
||||
fontSize: '0.6875rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'orange.400' : 'orange.500',
|
||||
paddingLeft: '4px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
alignItems: 'center',
|
||||
})}
|
||||
>
|
||||
{/* Small inline category label */}
|
||||
<span>⚠️</span>
|
||||
<span
|
||||
data-element="compact-category-label"
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
paddingLeft: '4px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.03em',
|
||||
})}
|
||||
>
|
||||
{cat.categoryName}
|
||||
Needs Attention
|
||||
</span>
|
||||
{/* Single student tile */}
|
||||
<StudentSelector
|
||||
students={cat.students as StudentWithProgress[]}
|
||||
onSelectStudent={handleSelectStudent}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onObserveSession={handleObserveSession}
|
||||
title=""
|
||||
selectedIds={selectedIds}
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Render full category (2+ students or has attention placeholder)
|
||||
const category = item.category
|
||||
const attentionCount =
|
||||
attentionCountsByBucket.get(bucket.bucket)?.get(category.category) ?? 0
|
||||
|
||||
return (
|
||||
<div
|
||||
key={category.category ?? 'null'}
|
||||
data-category={category.category ?? 'new'}
|
||||
>
|
||||
{/* Category header - sticky below bucket header */}
|
||||
<h3
|
||||
data-element="category-header"
|
||||
className={css({
|
||||
position: 'sticky',
|
||||
top: '195px', // Nav (80px) + Filter bar (~80px) + Bucket header (~35px)
|
||||
zIndex: Z_INDEX.STICKY_CATEGORY_HEADER,
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
marginBottom: '8px',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
paddingLeft: '4px',
|
||||
bg: isDark ? 'gray.900' : 'gray.50',
|
||||
})}
|
||||
>
|
||||
{category.categoryName}
|
||||
</h3>
|
||||
|
||||
{/* Student cards wrapper */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px',
|
||||
alignItems: 'stretch',
|
||||
})}
|
||||
>
|
||||
{/* Student cards */}
|
||||
{category.students.length > 0 && (
|
||||
</span>
|
||||
<StudentSelector
|
||||
students={category.students as StudentWithProgress[]}
|
||||
students={section.students as StudentWithProgress[]}
|
||||
onSelectStudent={handleSelectStudent}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onObserveSession={handleObserveSession}
|
||||
title=""
|
||||
selectedIds={selectedIds}
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Attention placeholder */}
|
||||
{attentionCount > 0 && (
|
||||
<div
|
||||
data-element="attention-placeholder"
|
||||
data-attention-count={attentionCount}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// Compact bucket (all single-student categories)
|
||||
return section.bucket.categories.map((cat) => (
|
||||
<div
|
||||
key={`${section.bucket.bucket}-${cat.category ?? 'null'}`}
|
||||
data-bucket={section.bucket.bucket}
|
||||
data-category={cat.category ?? 'new'}
|
||||
data-compact="true"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2px',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
data-element="compact-label"
|
||||
className={css({
|
||||
fontSize: '0.6875rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
paddingLeft: '4px',
|
||||
display: 'flex',
|
||||
gap: '4px',
|
||||
alignItems: 'center',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.300',
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
fontSize: '0.8125rem',
|
||||
textAlign: 'center',
|
||||
minHeight: '60px',
|
||||
flexShrink: 0,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.03em',
|
||||
color: isDark ? 'gray.600' : 'gray.350',
|
||||
})}
|
||||
>
|
||||
+{attentionCount} in Needs Attention
|
||||
</div>
|
||||
)}
|
||||
{section.bucket.bucketName}
|
||||
</span>
|
||||
<span className={css({ color: isDark ? 'gray.600' : 'gray.300' })}>
|
||||
·
|
||||
</span>
|
||||
<span>{cat.categoryName}</span>
|
||||
</span>
|
||||
<StudentSelector
|
||||
students={cat.students as StudentWithProgress[]}
|
||||
onSelectStudent={handleSelectStudent}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onObserveSession={handleObserveSession}
|
||||
title=""
|
||||
selectedIds={selectedIds}
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Full section
|
||||
const section = item.section
|
||||
|
||||
if (section.type === 'attention') {
|
||||
// Full attention section (multiple students)
|
||||
return (
|
||||
<div
|
||||
key="attention"
|
||||
data-bucket="attention"
|
||||
data-component="needs-attention-bucket"
|
||||
>
|
||||
<h2
|
||||
data-element="bucket-header"
|
||||
className={css({
|
||||
position: 'sticky',
|
||||
top: '160px',
|
||||
zIndex: Z_INDEX.STICKY_BUCKET_HEADER,
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '12px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.300',
|
||||
bg: isDark ? 'gray.900' : 'gray.50',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
})}
|
||||
>
|
||||
<span>⚠️</span>
|
||||
<span>Needs Attention</span>
|
||||
<span
|
||||
className={css({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minWidth: '20px',
|
||||
height: '20px',
|
||||
padding: '0 6px',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: isDark ? 'orange.700' : 'orange.500',
|
||||
color: 'white',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'bold',
|
||||
})}
|
||||
>
|
||||
{section.students.length}
|
||||
</span>
|
||||
</h2>
|
||||
<StudentSelector
|
||||
students={section.students as StudentWithProgress[]}
|
||||
onSelectStudent={handleSelectStudent}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onObserveSession={handleObserveSession}
|
||||
title=""
|
||||
selectedIds={selectedIds}
|
||||
hideAddButton
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Full bucket
|
||||
const bucket = section.bucket
|
||||
|
||||
return (
|
||||
<div key={bucket.bucket} data-bucket={bucket.bucket}>
|
||||
<h2
|
||||
data-element="bucket-header"
|
||||
className={css({
|
||||
position: 'sticky',
|
||||
top: '160px',
|
||||
zIndex: Z_INDEX.STICKY_BUCKET_HEADER,
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '12px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
bg: isDark ? 'gray.900' : 'gray.50',
|
||||
})}
|
||||
>
|
||||
{bucket.bucketName}
|
||||
</h2>
|
||||
|
||||
{/* Categories within bucket - grouped for compact display */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
// Group consecutive compact categories
|
||||
type CategoryRenderItem =
|
||||
| { type: 'compact-row'; categories: typeof bucket.categories }
|
||||
| { type: 'full'; category: (typeof bucket.categories)[0] }
|
||||
|
||||
const items: CategoryRenderItem[] = []
|
||||
let compactBuffer: typeof bucket.categories = []
|
||||
|
||||
for (const cat of bucket.categories) {
|
||||
if (isCategoryCompact(bucket, cat)) {
|
||||
compactBuffer.push(cat)
|
||||
} else {
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
compactBuffer = []
|
||||
}
|
||||
items.push({ type: 'full', category: cat })
|
||||
}
|
||||
}
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
}
|
||||
|
||||
return items.map((item, idx) => {
|
||||
if (item.type === 'compact-row') {
|
||||
// Render compact categories flowing together
|
||||
return (
|
||||
<div
|
||||
key={`compact-${idx}`}
|
||||
data-element="compact-category-row"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
alignItems: 'flex-start',
|
||||
})}
|
||||
>
|
||||
{item.categories.map((cat) => (
|
||||
<div
|
||||
key={cat.category ?? 'null'}
|
||||
data-category={cat.category ?? 'new'}
|
||||
data-compact="true"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
})}
|
||||
>
|
||||
{/* Small inline category label */}
|
||||
<span
|
||||
data-element="compact-category-label"
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
paddingLeft: '4px',
|
||||
})}
|
||||
>
|
||||
{cat.categoryName}
|
||||
</span>
|
||||
{/* Single student tile */}
|
||||
<StudentSelector
|
||||
students={cat.students as StudentWithProgress[]}
|
||||
onSelectStudent={handleSelectStudent}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onObserveSession={handleObserveSession}
|
||||
title=""
|
||||
selectedIds={selectedIds}
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Render full category (2+ students or has attention placeholder)
|
||||
const category = item.category
|
||||
const attentionCount =
|
||||
attentionCountsByBucket.get(bucket.bucket)?.get(category.category) ??
|
||||
0
|
||||
|
||||
return (
|
||||
<div
|
||||
key={category.category ?? 'null'}
|
||||
data-category={category.category ?? 'new'}
|
||||
>
|
||||
{/* Category header - sticky below bucket header */}
|
||||
<h3
|
||||
data-element="category-header"
|
||||
className={css({
|
||||
position: 'sticky',
|
||||
top: '195px', // Nav (80px) + Filter bar (~80px) + Bucket header (~35px)
|
||||
zIndex: Z_INDEX.STICKY_CATEGORY_HEADER,
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
marginBottom: '8px',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
paddingLeft: '4px',
|
||||
bg: isDark ? 'gray.900' : 'gray.50',
|
||||
})}
|
||||
>
|
||||
{category.categoryName}
|
||||
</h3>
|
||||
|
||||
{/* Student cards wrapper */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px',
|
||||
alignItems: 'stretch',
|
||||
})}
|
||||
>
|
||||
{/* Student cards */}
|
||||
{category.students.length > 0 && (
|
||||
<StudentSelector
|
||||
students={category.students as StudentWithProgress[]}
|
||||
onSelectStudent={handleSelectStudent}
|
||||
onToggleSelection={handleToggleSelection}
|
||||
onObserveSession={handleObserveSession}
|
||||
title=""
|
||||
selectedIds={selectedIds}
|
||||
hideAddButton
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Attention placeholder */}
|
||||
{attentionCount > 0 && (
|
||||
<div
|
||||
data-element="attention-placeholder"
|
||||
data-attention-count={attentionCount}
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.300',
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
fontSize: '0.8125rem',
|
||||
textAlign: 'center',
|
||||
minHeight: '60px',
|
||||
flexShrink: 0,
|
||||
})}
|
||||
>
|
||||
+{attentionCount} in Needs Attention
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -98,11 +98,351 @@ interface BucketData {
|
||||
// Component that replicates the exact structure from PracticeClient.tsx
|
||||
function GroupedStudentsDemo({
|
||||
buckets,
|
||||
needsAttentionStudents = [],
|
||||
isDark = false,
|
||||
}: {
|
||||
buckets: BucketData[]
|
||||
needsAttentionStudents?: StudentWithProgress[]
|
||||
isDark?: boolean
|
||||
}) {
|
||||
// Build unified sections list (attention first, then buckets)
|
||||
type Section =
|
||||
| { type: 'attention'; students: StudentWithProgress[] }
|
||||
| { type: 'bucket'; bucket: BucketData }
|
||||
|
||||
const allSections: Section[] = []
|
||||
if (needsAttentionStudents.length > 0) {
|
||||
allSections.push({ type: 'attention', students: needsAttentionStudents })
|
||||
}
|
||||
for (const bucket of buckets) {
|
||||
allSections.push({ type: 'bucket', bucket })
|
||||
}
|
||||
|
||||
// Helper to check if a category is compact
|
||||
const isCategoryCompact = (cat: CategoryData) =>
|
||||
cat.students.length === 1 && (cat.attentionCount ?? 0) === 0
|
||||
|
||||
// Helper to check if a bucket is compact
|
||||
const isBucketCompact = (bucket: BucketData) =>
|
||||
bucket.categories.every((cat) => isCategoryCompact(cat))
|
||||
|
||||
// Helper to check if a section is compact
|
||||
const isSectionCompact = (section: Section) => {
|
||||
if (section.type === 'attention') {
|
||||
return section.students.length === 1
|
||||
}
|
||||
return isBucketCompact(section.bucket)
|
||||
}
|
||||
|
||||
// Group consecutive compact sections
|
||||
type RenderItem =
|
||||
| { type: 'compact-sections'; sections: Section[] }
|
||||
| { type: 'full-section'; section: Section }
|
||||
|
||||
const renderItems: RenderItem[] = []
|
||||
let compactBuffer: Section[] = []
|
||||
|
||||
for (const section of allSections) {
|
||||
if (isSectionCompact(section)) {
|
||||
compactBuffer.push(section)
|
||||
} else {
|
||||
if (compactBuffer.length > 0) {
|
||||
renderItems.push({ type: 'compact-sections', sections: compactBuffer })
|
||||
compactBuffer = []
|
||||
}
|
||||
renderItems.push({ type: 'full-section', section })
|
||||
}
|
||||
}
|
||||
if (compactBuffer.length > 0) {
|
||||
renderItems.push({ type: 'compact-sections', sections: compactBuffer })
|
||||
}
|
||||
// Helper to render a compact section (single attention or compact bucket)
|
||||
const renderCompactSection = (section: Section, idx: number) => {
|
||||
if (section.type === 'attention') {
|
||||
// Single attention student - compact with label
|
||||
const student = section.students[0]
|
||||
return (
|
||||
<div
|
||||
key={`attention-compact-${idx}`}
|
||||
data-section="needs-attention"
|
||||
data-compact="true"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
data-element="compact-label"
|
||||
className={css({
|
||||
fontSize: '0.6875rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'orange.400' : 'orange.500',
|
||||
paddingLeft: '4px',
|
||||
})}
|
||||
>
|
||||
<span className={css({ textTransform: 'uppercase' })}>Needs Attention</span>
|
||||
</span>
|
||||
<StudentSelector
|
||||
students={[student]}
|
||||
onSelectStudent={() => {}}
|
||||
onToggleSelection={() => {}}
|
||||
title=""
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Compact bucket - all categories are single-student
|
||||
const bucket = section.bucket
|
||||
return bucket.categories.map((cat, catIdx) => (
|
||||
<div
|
||||
key={`${bucket.bucket}-${cat.category}-${catIdx}`}
|
||||
data-bucket={bucket.bucket}
|
||||
data-category={cat.category}
|
||||
data-compact="true"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
data-element="compact-label"
|
||||
className={css({
|
||||
fontSize: '0.6875rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
paddingLeft: '4px',
|
||||
})}
|
||||
>
|
||||
<span className={css({ textTransform: 'uppercase' })}>{bucket.bucketName}</span>
|
||||
<span className={css({ margin: '0 4px' })}>·</span>
|
||||
<span>{cat.categoryName}</span>
|
||||
</span>
|
||||
<StudentSelector
|
||||
students={cat.students}
|
||||
onSelectStudent={() => {}}
|
||||
onToggleSelection={() => {}}
|
||||
title=""
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
// Helper to render a full section (multiple attention or non-compact bucket)
|
||||
const renderFullSection = (section: Section) => {
|
||||
if (section.type === 'attention') {
|
||||
// Full Needs Attention section
|
||||
return (
|
||||
<div key="needs-attention-full" data-section="needs-attention">
|
||||
<h2
|
||||
data-element="attention-header"
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '12px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.200',
|
||||
})}
|
||||
>
|
||||
⚠️ Needs Attention
|
||||
</h2>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px',
|
||||
})}
|
||||
>
|
||||
<StudentSelector
|
||||
students={section.students}
|
||||
onSelectStudent={() => {}}
|
||||
onToggleSelection={() => {}}
|
||||
title=""
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Full bucket with categories
|
||||
const bucket = section.bucket
|
||||
return (
|
||||
<div key={bucket.bucket} data-bucket={bucket.bucket}>
|
||||
<h2
|
||||
data-element="bucket-header"
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '12px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
{bucket.bucketName}
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
// Group consecutive compact categories within the bucket
|
||||
type CatRenderItem =
|
||||
| { type: 'compact-row'; categories: CategoryData[] }
|
||||
| { type: 'full'; category: CategoryData }
|
||||
|
||||
const items: CatRenderItem[] = []
|
||||
let compactBuffer: CategoryData[] = []
|
||||
|
||||
for (const cat of bucket.categories) {
|
||||
if (isCategoryCompact(cat)) {
|
||||
compactBuffer.push(cat)
|
||||
} else {
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
compactBuffer = []
|
||||
}
|
||||
items.push({ type: 'full', category: cat })
|
||||
}
|
||||
}
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
}
|
||||
|
||||
return items.map((item, idx) => {
|
||||
if (item.type === 'compact-row') {
|
||||
return (
|
||||
<div
|
||||
key={`compact-${idx}`}
|
||||
data-element="compact-category-row"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
alignItems: 'flex-start',
|
||||
})}
|
||||
>
|
||||
{item.categories.map((cat) => (
|
||||
<div
|
||||
key={cat.category}
|
||||
data-category={cat.category}
|
||||
data-compact="true"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
data-element="compact-category-label"
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
paddingLeft: '4px',
|
||||
})}
|
||||
>
|
||||
{cat.categoryName}
|
||||
</span>
|
||||
<StudentSelector
|
||||
students={cat.students}
|
||||
onSelectStudent={() => {}}
|
||||
onToggleSelection={() => {}}
|
||||
title=""
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const category = item.category
|
||||
return (
|
||||
<div key={category.category} data-category={category.category}>
|
||||
<h3
|
||||
data-element="category-header"
|
||||
className={css({
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
marginBottom: '8px',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
paddingLeft: '4px',
|
||||
})}
|
||||
>
|
||||
{category.categoryName}
|
||||
</h3>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px',
|
||||
alignItems: 'stretch',
|
||||
})}
|
||||
>
|
||||
<StudentSelector
|
||||
students={category.students}
|
||||
onSelectStudent={() => {}}
|
||||
onToggleSelection={() => {}}
|
||||
title=""
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
{(category.attentionCount ?? 0) > 0 && (
|
||||
<div
|
||||
data-element="attention-placeholder"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.300',
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
fontSize: '0.8125rem',
|
||||
textAlign: 'center',
|
||||
minHeight: '120px',
|
||||
minWidth: '150px',
|
||||
})}
|
||||
>
|
||||
+{category.attentionCount} in Needs Attention
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
@@ -120,182 +460,26 @@ function GroupedStudentsDemo({
|
||||
gap: '24px',
|
||||
})}
|
||||
>
|
||||
{buckets.map((bucket) => (
|
||||
<div key={bucket.bucket} data-bucket={bucket.bucket}>
|
||||
{/* Bucket header (e.g., "OLDER", "TODAY") */}
|
||||
<h2
|
||||
data-element="bucket-header"
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '12px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
{bucket.bucketName}
|
||||
</h2>
|
||||
|
||||
{/* Categories within bucket - grouped for compact display */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
// Group consecutive compact categories (1 student, no attention placeholder)
|
||||
type RenderItem =
|
||||
| { type: 'compact-row'; categories: CategoryData[] }
|
||||
| { type: 'full'; category: CategoryData }
|
||||
|
||||
const items: RenderItem[] = []
|
||||
let compactBuffer: CategoryData[] = []
|
||||
|
||||
for (const cat of bucket.categories) {
|
||||
const isCompact = cat.students.length === 1 && (cat.attentionCount ?? 0) === 0
|
||||
|
||||
if (isCompact) {
|
||||
compactBuffer.push(cat)
|
||||
} else {
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
compactBuffer = []
|
||||
}
|
||||
items.push({ type: 'full', category: cat })
|
||||
}
|
||||
}
|
||||
if (compactBuffer.length > 0) {
|
||||
items.push({ type: 'compact-row', categories: compactBuffer })
|
||||
}
|
||||
|
||||
return items.map((item, idx) => {
|
||||
if (item.type === 'compact-row') {
|
||||
// Render compact categories flowing together
|
||||
return (
|
||||
<div
|
||||
key={`compact-${idx}`}
|
||||
data-element="compact-category-row"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
alignItems: 'flex-start',
|
||||
})}
|
||||
>
|
||||
{item.categories.map((cat) => (
|
||||
<div
|
||||
key={cat.category}
|
||||
data-category={cat.category}
|
||||
data-compact="true"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
})}
|
||||
>
|
||||
{/* Small inline category label */}
|
||||
<span
|
||||
data-element="compact-category-label"
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
paddingLeft: '4px',
|
||||
})}
|
||||
>
|
||||
{cat.categoryName}
|
||||
</span>
|
||||
{/* Single student tile */}
|
||||
<StudentSelector
|
||||
students={cat.students}
|
||||
onSelectStudent={() => {}}
|
||||
onToggleSelection={() => {}}
|
||||
title=""
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Render full category (2+ students or has attention placeholder)
|
||||
const category = item.category
|
||||
return (
|
||||
<div key={category.category} data-category={category.category}>
|
||||
{/* Category header (sticky in real app) */}
|
||||
<h3
|
||||
data-element="category-header"
|
||||
className={css({
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 'medium',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
marginBottom: '8px',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
paddingLeft: '4px',
|
||||
})}
|
||||
>
|
||||
{category.categoryName}
|
||||
</h3>
|
||||
|
||||
{/* Student cards wrapper */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px',
|
||||
alignItems: 'stretch',
|
||||
})}
|
||||
>
|
||||
{/* Student cards - NOT compact for multi-student */}
|
||||
<StudentSelector
|
||||
students={category.students}
|
||||
onSelectStudent={() => {}}
|
||||
onToggleSelection={() => {}}
|
||||
title=""
|
||||
hideAddButton
|
||||
compact
|
||||
/>
|
||||
|
||||
{/* Attention placeholder */}
|
||||
{(category.attentionCount ?? 0) > 0 && (
|
||||
<div
|
||||
data-element="attention-placeholder"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px dashed',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.300',
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
fontSize: '0.8125rem',
|
||||
textAlign: 'center',
|
||||
minHeight: '120px',
|
||||
minWidth: '150px',
|
||||
})}
|
||||
>
|
||||
+{category.attentionCount} in Needs Attention
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{renderItems.map((item, idx) => {
|
||||
if (item.type === 'compact-sections') {
|
||||
// Render compact sections flowing together
|
||||
return (
|
||||
<div
|
||||
key={`compact-sections-${idx}`}
|
||||
data-element="compact-sections-row"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
alignItems: 'flex-start',
|
||||
})}
|
||||
>
|
||||
{item.sections.map((section, sIdx) => renderCompactSection(section, sIdx))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return renderFullSection(item.section)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -622,3 +806,208 @@ export const RealisticScenario: Story = {
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NEEDS ATTENTION STORIES
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Single student needing attention - renders compact, flows with other compact sections
|
||||
*/
|
||||
export const NeedsAttentionSingle: Story = {
|
||||
render: () => (
|
||||
<GroupedStudentsDemo
|
||||
needsAttentionStudents={[students.sonia]}
|
||||
buckets={[
|
||||
{
|
||||
bucket: 'older',
|
||||
bucketName: 'Older',
|
||||
categories: [
|
||||
{
|
||||
category: 'five-comp-add',
|
||||
categoryName: 'Five Complements (Add)',
|
||||
students: [students.marcus],
|
||||
},
|
||||
{
|
||||
category: 'ten-comp-sub',
|
||||
categoryName: 'Ten Complements (Sub)',
|
||||
students: [students.luna],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple students needing attention - renders full section with sticky header
|
||||
*/
|
||||
export const NeedsAttentionMultiple: Story = {
|
||||
render: () => (
|
||||
<GroupedStudentsDemo
|
||||
needsAttentionStudents={[students.sonia, students.marcus, students.luna]}
|
||||
buckets={[
|
||||
{
|
||||
bucket: 'older',
|
||||
bucketName: 'Older',
|
||||
categories: [
|
||||
{
|
||||
category: 'five-comp-add',
|
||||
categoryName: 'Five Complements (Add)',
|
||||
students: [students.alex],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
/**
|
||||
* Single attention student flows together with single-student compact buckets
|
||||
*/
|
||||
export const NeedsAttentionWithCompactBuckets: Story = {
|
||||
render: () => (
|
||||
<GroupedStudentsDemo
|
||||
needsAttentionStudents={[students.sonia]}
|
||||
buckets={[
|
||||
{
|
||||
bucket: 'today',
|
||||
bucketName: 'Today',
|
||||
categories: [
|
||||
{
|
||||
category: 'five-comp-add',
|
||||
categoryName: 'Five Comp (Add)',
|
||||
students: [students.marcus],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
bucket: 'thisWeek',
|
||||
bucketName: 'This Week',
|
||||
categories: [
|
||||
{
|
||||
category: 'ten-comp-sub',
|
||||
categoryName: 'Ten Comp (Sub)',
|
||||
students: [students.luna],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple attention students (full section) followed by compact buckets
|
||||
*/
|
||||
export const NeedsAttentionFullThenCompact: Story = {
|
||||
render: () => (
|
||||
<GroupedStudentsDemo
|
||||
needsAttentionStudents={[students.sonia, students.marcus]}
|
||||
buckets={[
|
||||
{
|
||||
bucket: 'older',
|
||||
bucketName: 'Older',
|
||||
categories: [
|
||||
{
|
||||
category: 'five-comp-add',
|
||||
categoryName: 'Five Comp (Add)',
|
||||
students: [students.luna],
|
||||
},
|
||||
{
|
||||
category: 'ten-comp-sub',
|
||||
categoryName: 'Ten Comp (Sub)',
|
||||
students: [students.alex],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete realistic scenario with attention section
|
||||
*/
|
||||
export const NeedsAttentionRealistic: Story = {
|
||||
render: () => (
|
||||
<GroupedStudentsDemo
|
||||
needsAttentionStudents={[students.kai]}
|
||||
buckets={[
|
||||
{
|
||||
bucket: 'today',
|
||||
bucketName: 'Today',
|
||||
categories: [
|
||||
{
|
||||
category: 'five-comp-add',
|
||||
categoryName: 'Five Complements (Addition)',
|
||||
students: [students.sonia, students.marcus],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
bucket: 'thisWeek',
|
||||
bucketName: 'This Week',
|
||||
categories: [
|
||||
{
|
||||
category: 'ten-comp-sub',
|
||||
categoryName: 'Ten Comp (Sub)',
|
||||
students: [students.luna],
|
||||
},
|
||||
{
|
||||
category: 'ten-comp-add',
|
||||
categoryName: 'Ten Comp (Add)',
|
||||
students: [students.alex],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
bucket: 'older',
|
||||
bucketName: 'Older',
|
||||
categories: [
|
||||
{
|
||||
category: 'basic-add',
|
||||
categoryName: 'Basic Addition',
|
||||
students: [students.maya],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
/**
|
||||
* Dark mode with needs attention section
|
||||
*/
|
||||
export const NeedsAttentionDarkMode: Story = {
|
||||
render: () => (
|
||||
<GroupedStudentsDemo
|
||||
isDark
|
||||
needsAttentionStudents={[students.sonia, students.marcus]}
|
||||
buckets={[
|
||||
{
|
||||
bucket: 'older',
|
||||
bucketName: 'Older',
|
||||
categories: [
|
||||
{
|
||||
category: 'five-comp-add',
|
||||
categoryName: 'Five Complements (Addition)',
|
||||
students: [students.luna],
|
||||
},
|
||||
{
|
||||
category: 'ten-comp-sub',
|
||||
categoryName: 'Ten Complements (Subtraction)',
|
||||
students: [students.alex],
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
parameters: {
|
||||
backgrounds: { default: 'dark' },
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user