Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions openspec/changes/fix-home-announcements-width/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Context

The home screen displays announcement cards in a horizontal carousel. Currently, each card occupies a large portion of the screen width, limiting how many announcements are visible at once and making the section feel oversized relative to the rest of the home screen. The goal is to reduce the card width so more announcements are visible without scrolling, without changing any data-fetching, navigation, or structural behaviour.

## Goals / Non-Goals

**Goals:**
- Announcement cards in the carousel are narrower, letting users see more at a glance.
- The change is isolated to the carousel layout — no structural or data changes.

**Non-Goals:**
- Changing card height or content layout.
- Affecting the single-card rendering path.
- Modifying navigation, data fetching, or caching behaviour.
- Redesigning the announcement card visual design.

## Decisions

**Reduce announcement card width**
The announcement carousel card width should be reduced so the section feels less dominant on the home screen and users can see more content at a glance. No new abstraction or shared token is needed — this is a localised layout adjustment within the courses package.

## Risks / Trade-offs

- **Card content may feel more compact** → Announcement titles and summaries may appear more condensed. Text truncation (already in place) handles this gracefully.
- **Wider-than-expected content** → If announcement content is longer, the narrower card may truncate more aggressively, but this is an acceptable trade-off for the improved layout density.
22 changes: 22 additions & 0 deletions openspec/changes/fix-home-announcements-width/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Why

The Updates & Announcements section on the home screen occupies too much horizontal space, causing each card to appear oversized and limiting how many announcements are visible at a glance. Reducing the card width would make better use of available space and allow more announcements to be seen without scrolling.

## What Changes

- Reduce the width of announcement cards in the carousel so each card takes up less horizontal space.
- Card height is unchanged — only the horizontal footprint is affected.
- When only a single announcement exists, the layout is unaffected.

## Capabilities

### New Capabilities
<!-- None — this is a visual layout fix -->

### Modified Capabilities
- `dashboard-announcements`: Announcement cards in the carousel are narrower, allowing more cards to be visible at a glance.

## Impact

- **Package**: `courses`
- No API or data changes. No breaking changes. No other packages affected.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## MODIFIED Requirements

### Requirement: Display Announcement Cards at Reduced Width
The announcements carousel SHALL render each card at a reduced width, so that more announcement cards are visible at a glance without horizontal scrolling.

#### Scenario: Multiple announcements displayed in carousel
- **WHEN** the announcements section renders more than one announcement card in the carousel
- **THEN** each announcement card SHALL occupy a reduced horizontal width compared to the current full-width behaviour
- **AND** the card height remains unchanged

#### Scenario: Single announcement displayed
- **WHEN** only one announcement card is present
- **THEN** the card rendering path is unaffected by this requirement
10 changes: 10 additions & 0 deletions openspec/changes/fix-home-announcements-width/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## 1. Implementation

- [x] 1.1 Reduce the carousel card width in the announcements section so cards are narrower and the next card is partially visible beyond the screen edge
- [x] 1.2 Verify card height is unchanged after the width adjustment

## 2. Verification

- [x] 2.1 Confirm that with multiple announcements, the next card peeks out beyond the screen edge as a scroll affordance
- [x] 2.2 Confirm that with a single announcement, the layout is unaffected
- [x] 2.3 Confirm no changes to navigation, data fetching, or card content layout
126 changes: 72 additions & 54 deletions packages/courses/lib/widgets/promotional_banners.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@ class UpdatesAnnouncementsSection extends StatelessWidget {
l10n.updatesAnnouncementsTitle,
color: design.colors.textPrimary,
),
GestureDetector(
AppSemantics.button(
label: l10n.viewAllAction,
onTap: onViewAll,
child: AppText.labelSmall(
l10n.viewAllAction,
color: design.colors.primary,
child: GestureDetector(
onTap: onViewAll,
child: AppText.labelSmall(
l10n.viewAllAction,
color: design.colors.primary,
),
),
),
],
Expand Down Expand Up @@ -88,7 +92,8 @@ class PromotionalBanners extends StatelessWidget {
return AppCarousel(
height: 110 * scale,
showDots: false,
viewportFraction: 0.88,
viewportFraction:
(320.0 / MediaQuery.sizeOf(context).width).clamp(0.30, 0.85),
padEnds: false,
itemCount: posts.length,
itemPadding: EdgeInsets.only(left: design.spacing.md),
Expand All @@ -113,61 +118,74 @@ class AnnouncementCard extends StatelessWidget {
Widget build(BuildContext context) {
final design = Design.of(context);

// Create a deterministic color palette based on item index
// Create a deterministic color palette based on item index using design tokens
final colors = [
(bg: const Color(0xFFECFDF5), text: const Color(0xFF065F46)), // Green
(bg: const Color(0xFFFAF5FF), text: const Color(0xFF6B21A8)), // Purple
(bg: const Color(0xFFFFFBEB), text: const Color(0xFF92400E)), // Amber
(
bg: design.colors.success.withValues(alpha: 0.1),
text: design.colors.success
), // Green
(
bg: design.colors.primary.withValues(alpha: 0.1),
text: design.colors.primary
), // Purple
(
bg: design.colors.warning.withValues(alpha: 0.1),
text: design.colors.warning
), // Amber
];
final colorScheme = colors[index % colors.length];

return GestureDetector(
behavior: HitTestBehavior.opaque,
return AppSemantics.button(
label: post.title,
onTap: onTap,
child: Container(
padding: EdgeInsets.all(design.spacing.md),
decoration: BoxDecoration(
color: colorScheme.bg,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: design.colors.border.withValues(alpha: 0.06),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
AppText.body(
post.title,
color: colorScheme.text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 2),
AppText.bodySmall(
post.summary.replaceAll(RegExp(r'[\r\n]+'), ' ').trim(),
color: colorScheme.text.withValues(alpha: 0.8),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
padding: EdgeInsets.all(design.spacing.md),
decoration: BoxDecoration(
color: colorScheme.bg,
borderRadius: design.radius.card,
boxShadow: [
BoxShadow(
color: design.colors.border.withValues(alpha: 0.06),
blurRadius: 10,
offset: const Offset(0, 4),
),
),
const SizedBox(width: 12),
Icon(
LucideIcons.chevronRight,
size: 20,
color: colorScheme.text.withValues(alpha: 0.6),
),
],
],
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
AppText.body(
post.title,
color: colorScheme.text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 2),
AppText.bodySmall(
post.summary.replaceAll(RegExp(r'[\r\n]+'), ' ').trim(),
color: colorScheme.text.withValues(alpha: 0.8),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 12),
Icon(
LucideIcons.chevronRight,
size: 20,
color: colorScheme.text.withValues(alpha: 0.6),
),
],
),
),
),
);
Expand Down