Accordion
In ProgressDark modeRTLExpandable and collapsible content sections.
Installation
Copy the component into your project using the CLI:
$ronakcn add accordion
Usage
Import and use the component in your Flutter widget tree:
lib/pages/example.dart
import 'package:flutter/material.dart';
import '../components/accordion/accordion.dart';
class ExamplePage extends StatelessWidget {
const ExamplePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RcnAccordion Example')),
body: Center(
child: RcnAccordion(
// Configure props here
),
),
);
}
}Variants
Accordion ships with the following variants:
default
Only one section open at a time.
multiple
Multiple sections can be open simultaneously.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items* | List<AccordionItem> | — | List of accordion sections. |
allowMultiple | bool | false | Allow multiple sections to be open. |
initialOpenIndex | int? | null | Index of initially opened section. |
Source files
lib/components/ui/accordion.dart
import 'package:flutter/material.dart';
class AccordionItem {
const AccordionItem({required this.title, required this.content, this.subtitle});
final String title;
final String content;
final String? subtitle;
}
class RcnAccordion extends StatefulWidget {
const RcnAccordion({super.key, required this.items, this.allowMultiple = false, this.initialOpenIndex});
final List<AccordionItem> items;
final bool allowMultiple;
final int? initialOpenIndex;
@override
State<RcnAccordion> createState() => _RcnAccordionState();
}
class _RcnAccordionState extends State<RcnAccordion> {
late Set<int> _openIndices;
@override
void initState() {
super.initState();
_openIndices = widget.initialOpenIndex != null ? {widget.initialOpenIndex!} : {};
}
void _toggle(int index) {
setState(() {
if (_openIndices.contains(index)) {
_openIndices.remove(index);
} else {
if (!widget.allowMultiple) _openIndices.clear();
_openIndices.add(index);
}
});
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Column(
children: List.generate(widget.items.length, (i) {
final item = widget.items[i];
final isOpen = _openIndices.contains(i);
return Column(
children: [
if (i > 0) Divider(height: 1, color: colors.outline.withOpacity(0.2)),
InkWell(
onTap: () => _toggle(i),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 14),
child: Row(
children: [
Expanded(child: Text(item.title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface))),
AnimatedRotation(
turns: isOpen ? 0.5 : 0,
duration: const Duration(milliseconds: 200),
child: Icon(Icons.keyboard_arrow_down, size: 18, color: colors.onSurface.withOpacity(0.6)),
),
],
),
),
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 200),
crossFadeState: isOpen ? CrossFadeState.showFirst : CrossFadeState.showSecond,
firstChild: Padding(
padding: const EdgeInsets.only(bottom: 14),
child: Text(item.content, style: TextStyle(fontSize: 13, color: colors.onSurface.withOpacity(0.7))),
),
secondChild: const SizedBox.shrink(),
),
],
);
}),
);
}
}Version
0.1.0 · display category