add ClideFilterBox widget to all sidebar panes
Shared filter box with search icon, debounced input, and clear button. Applied to all six left-panel panes for consistent filtering: Files (flat path results when filtering), Git (filter staged/unstaged/untracked by path), Decisions (by ID/title/domain), Tickets (by ID/title/status), Problems (by source/message), and pql Query (replaces custom _QueryInput). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,13 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
|
||||
### Added
|
||||
|
||||
- Shared ClideFilterBox widget with search icon, clear button, and
|
||||
debounced input. Applied consistently across all sidebar panes:
|
||||
Files (path filter with flat results), Git (filter staged/unstaged
|
||||
by path), Decisions (filter by ID/title/domain), Tickets (filter
|
||||
by ID/title/status), Problems (filter by source/message), and
|
||||
pql Query (replaces custom input).
|
||||
|
||||
- Interaction model from Wireframe Flows v3: eight new D-records
|
||||
(D-047 through D-054) and five Q-records (Q-026 through Q-030)
|
||||
codifying layout invariants, chrome budget, editor mode, context
|
||||
|
||||
@@ -16,6 +16,7 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
List<_DecisionEntry> _decisions = [];
|
||||
String? _error;
|
||||
bool _loading = true;
|
||||
String _filter = '';
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -70,12 +71,18 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
child: ClideText('No decisions found.\nRun `pql decisions sync` to index.', muted: true),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: _decisions.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final d = _decisions[i];
|
||||
return _DecisionRow(entry: d, tokens: tokens);
|
||||
},
|
||||
final lf = _filter.toLowerCase();
|
||||
final filtered = lf.isEmpty ? _decisions : _decisions.where((d) => d.id.toLowerCase().contains(lf) || d.title.toLowerCase().contains(lf) || (d.domain ?? '').toLowerCase().contains(lf)).toList();
|
||||
return Column(
|
||||
children: [
|
||||
ClideFilterBox(hint: 'Filter decisions…', onChanged: (v) => setState(() => _filter = v)),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filtered.length,
|
||||
itemBuilder: (ctx, i) => _DecisionRow(entry: filtered[i], tokens: tokens),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,14 @@ class FileTreeController extends ChangeNotifier {
|
||||
final Map<String, List<FileEntry>> _entries = {};
|
||||
List<FileEntry>? entriesFor(String path) => _entries[path];
|
||||
|
||||
List<FileEntry> allLoadedEntries() {
|
||||
final out = <FileEntry>[];
|
||||
for (final list in _entries.values) {
|
||||
out.addAll(list.where((e) => !e.isDirectory));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Initial boot: resolve the workspace root, load the root dir,
|
||||
/// subscribe to `files.changed` events.
|
||||
Future<void> load() async {
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:clide/src/files/listing.dart' show FileEntry;
|
||||
|
||||
import 'file_tree_controller.dart';
|
||||
|
||||
/// Sidebar panel rendering the workspace file tree.
|
||||
@@ -24,6 +26,7 @@ class FileTreeView extends StatefulWidget {
|
||||
|
||||
class _FileTreeViewState extends State<FileTreeView> {
|
||||
FileTreeController? _controller;
|
||||
String _filter = '';
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -61,31 +64,45 @@ class _FileTreeViewState extends State<FileTreeView> {
|
||||
);
|
||||
}
|
||||
final rootName = root.split(Platform.pathSeparator).last;
|
||||
return Semantics(
|
||||
label: 'file tree — $rootName',
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_DirRow(
|
||||
name: rootName,
|
||||
path: '',
|
||||
controller: c,
|
||||
depth: 0,
|
||||
return Column(
|
||||
children: [
|
||||
ClideFilterBox(hint: 'Filter files…', onChanged: (v) => setState(() => _filter = v)),
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
label: 'file tree — $rootName',
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_filter.isEmpty) ...[
|
||||
_DirRow(name: rootName, path: '', controller: c, depth: 0),
|
||||
if (c.isExpanded('')) _Children(path: '', controller: c, depth: 1),
|
||||
] else
|
||||
..._filteredEntries(c),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (c.isExpanded(''))
|
||||
_Children(path: '', controller: c, depth: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _filteredEntries(FileTreeController c) {
|
||||
final lowerFilter = _filter.toLowerCase();
|
||||
final matches = c.allLoadedEntries().where((e) {
|
||||
return e.path.toLowerCase().contains(lowerFilter) || e.name.toLowerCase().contains(lowerFilter);
|
||||
}).toList();
|
||||
return [
|
||||
for (final e in matches) _FilteredFileRow(entry: e),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class _Children extends StatelessWidget {
|
||||
@@ -269,3 +286,37 @@ class _RowState extends State<_Row> {
|
||||
}
|
||||
}
|
||||
|
||||
class _FilteredFileRow extends StatefulWidget {
|
||||
const _FilteredFileRow({required this.entry});
|
||||
final FileEntry entry;
|
||||
|
||||
@override
|
||||
State<_FilteredFileRow> createState() => _FilteredFileRowState();
|
||||
}
|
||||
|
||||
class _FilteredFileRowState extends State<_FilteredFileRow> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (_) => setState(() => _hover = true),
|
||||
onExit: (_) => setState(() => _hover = false),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
final kernel = ClideKernel.of(context);
|
||||
unawaited(kernel.ipc.request('editor.open', args: {'path': widget.entry.path}));
|
||||
},
|
||||
child: Container(
|
||||
color: _hover ? tokens.sidebarItemHover : null,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
|
||||
child: ClideText(widget.entry.path, maxLines: 1, overflow: TextOverflow.ellipsis, color: tokens.sidebarForeground),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ class _GitPanelViewState extends State<GitPanelView> {
|
||||
GitController? _controller;
|
||||
final TextEditingController _commitMsg = TextEditingController();
|
||||
final FocusNode _commitFocus = FocusNode();
|
||||
String _filter = '';
|
||||
|
||||
List<Map<String, Object?>> _applyFilter(List<Map<String, Object?>> entries) {
|
||||
if (_filter.isEmpty) return entries;
|
||||
final lf = _filter.toLowerCase();
|
||||
return entries.where((e) => ((e['path'] as String?) ?? '').toLowerCase().contains(lf)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -66,7 +73,10 @@ class _GitPanelViewState extends State<GitPanelView> {
|
||||
label: 'git panel',
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ClideFilterBox(hint: 'Filter changes…', onChanged: (v) => setState(() => _filter = v)),
|
||||
Expanded(child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
@@ -98,13 +108,13 @@ class _GitPanelViewState extends State<GitPanelView> {
|
||||
if (c.conflicted.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Merge conflicts',
|
||||
entries: c.conflicted,
|
||||
entries: _applyFilter(c.conflicted),
|
||||
actions: const [],
|
||||
),
|
||||
if (c.staged.isNotEmpty) ...[
|
||||
_FileGroup(
|
||||
label: 'Staged',
|
||||
entries: c.staged,
|
||||
entries: _applyFilter(c.staged),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Unstage all',
|
||||
@@ -122,7 +132,7 @@ class _GitPanelViewState extends State<GitPanelView> {
|
||||
if (c.unstaged.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Changes',
|
||||
entries: c.unstaged,
|
||||
entries: _applyFilter(c.unstaged),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Stage all',
|
||||
@@ -135,7 +145,7 @@ class _GitPanelViewState extends State<GitPanelView> {
|
||||
if (c.untracked.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Untracked',
|
||||
entries: c.untracked,
|
||||
entries: _applyFilter(c.untracked),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Stage all',
|
||||
@@ -151,7 +161,9 @@ class _GitPanelViewState extends State<GitPanelView> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -19,8 +19,6 @@ class PqlPanelView extends StatefulWidget {
|
||||
|
||||
class _PqlPanelViewState extends State<PqlPanelView> {
|
||||
PqlController? _controller;
|
||||
final TextEditingController _queryInput = TextEditingController();
|
||||
final FocusNode _queryFocus = FocusNode();
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -34,8 +32,6 @@ class _PqlPanelViewState extends State<PqlPanelView> {
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
_queryInput.dispose();
|
||||
_queryFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -56,10 +52,11 @@ class _PqlPanelViewState extends State<PqlPanelView> {
|
||||
children: [
|
||||
_ViewTabs(controller: c),
|
||||
if (c.view == PqlView.query)
|
||||
_QueryInput(
|
||||
input: _queryInput,
|
||||
focus: _queryFocus,
|
||||
controller: c,
|
||||
ClideFilterBox(
|
||||
hint: 'PQL query…',
|
||||
debounce: Duration.zero,
|
||||
onChanged: (_) {},
|
||||
onSubmitted: (v) => unawaited(c.runQuery(v)),
|
||||
),
|
||||
if (c.error != null)
|
||||
Padding(
|
||||
@@ -158,50 +155,6 @@ class _ViewTabs extends StatelessWidget {
|
||||
};
|
||||
}
|
||||
|
||||
class _QueryInput extends StatelessWidget {
|
||||
const _QueryInput({
|
||||
required this.input,
|
||||
required this.focus,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final TextEditingController input;
|
||||
final FocusNode focus;
|
||||
final PqlController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Semantics(
|
||||
label: 'pql query',
|
||||
textField: true,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: tokens.globalBorder),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
child: EditableText(
|
||||
controller: input,
|
||||
focusNode: focus,
|
||||
style: TextStyle(
|
||||
fontFamily: clideMonoFamily,
|
||||
fontSize: clideFontMono,
|
||||
color: tokens.globalForeground,
|
||||
),
|
||||
cursorColor: tokens.globalFocus,
|
||||
backgroundCursorColor: tokens.globalFocus,
|
||||
maxLines: 1,
|
||||
onSubmitted: (_) =>
|
||||
unawaited(controller.runQuery(input.text)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FileRow extends StatefulWidget {
|
||||
const _FileRow({required this.entry});
|
||||
final Map<String, Object?> entry;
|
||||
|
||||
@@ -18,6 +18,7 @@ class ProblemsView extends StatefulWidget {
|
||||
|
||||
class _ProblemsViewState extends State<ProblemsView> {
|
||||
ProblemsController? _controller;
|
||||
String _filter = '';
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -46,66 +47,46 @@ class _ProblemsViewState extends State<ProblemsView> {
|
||||
label: 'problems panel',
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ClideText(
|
||||
'Problems (${c.problems.length})',
|
||||
fontSize: clideFontCaption,
|
||||
color: tokens.sidebarForeground,
|
||||
),
|
||||
),
|
||||
Semantics(
|
||||
button: true,
|
||||
label: 'refresh problems',
|
||||
child: GestureDetector(
|
||||
onTap: () => unawaited(c.refresh()),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: ClideText(
|
||||
'Refresh',
|
||||
fontSize: clideFontCaption,
|
||||
color: tokens.sidebarForeground,
|
||||
),
|
||||
child: () {
|
||||
final lf = _filter.toLowerCase();
|
||||
final filtered = lf.isEmpty ? c.problems : c.problems.where((p) => p.message.toLowerCase().contains(lf) || p.source.toLowerCase().contains(lf)).toList();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ClideFilterBox(hint: 'Filter problems…', onChanged: (v) => setState(() => _filter = v)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: ClideText('Problems (${filtered.length})', fontSize: clideFontCaption, color: tokens.sidebarForeground)),
|
||||
Semantics(
|
||||
button: true,
|
||||
label: 'refresh problems',
|
||||
child: GestureDetector(
|
||||
onTap: () => unawaited(c.refresh()),
|
||||
child: MouseRegion(cursor: SystemMouseCursors.click, child: ClideText('Refresh', fontSize: clideFontCaption, color: tokens.sidebarForeground)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (c.loading && c.problems.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: ClideText('Scanning…', muted: true),
|
||||
),
|
||||
if (!c.loading && c.problems.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: ClideText(
|
||||
'No problems found.',
|
||||
muted: true,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (final p in c.problems) _ProblemRow(problem: p),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (c.loading && c.problems.isEmpty)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('Scanning…', muted: true)),
|
||||
if (!c.loading && filtered.isEmpty)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('No problems found.', muted: true)),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [for (final p in filtered) _ProblemRow(problem: p)],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ class _TicketsViewState extends State<TicketsView> {
|
||||
List<_TicketEntry> _tickets = [];
|
||||
String? _error;
|
||||
bool _loading = true;
|
||||
String _filter = '';
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -70,12 +71,18 @@ class _TicketsViewState extends State<TicketsView> {
|
||||
child: ClideText('No tickets found.\nRun `pql ticket new` to create one.', muted: true),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: _tickets.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final t = _tickets[i];
|
||||
return _TicketRow(entry: t, tokens: tokens);
|
||||
},
|
||||
final lf = _filter.toLowerCase();
|
||||
final filtered = lf.isEmpty ? _tickets : _tickets.where((t) => t.id.toLowerCase().contains(lf) || t.title.toLowerCase().contains(lf) || (t.status ?? '').toLowerCase().contains(lf)).toList();
|
||||
return Column(
|
||||
children: [
|
||||
ClideFilterBox(hint: 'Filter tickets…', onChanged: (v) => setState(() => _filter = v)),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filtered.length,
|
||||
itemBuilder: (ctx, i) => _TicketRow(entry: filtered[i], tokens: tokens),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide/kernel/src/theme/controller.dart';
|
||||
import 'package:clide/widgets/src/clide_icon.dart';
|
||||
import 'package:clide/widgets/src/icons/phosphor.dart';
|
||||
import 'package:clide/widgets/src/typography.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ClideFilterBox extends StatefulWidget {
|
||||
const ClideFilterBox({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
this.hint = 'Filter…',
|
||||
this.debounce = const Duration(milliseconds: 200),
|
||||
this.onSubmitted,
|
||||
});
|
||||
|
||||
final ValueChanged<String> onChanged;
|
||||
final String hint;
|
||||
final Duration debounce;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
|
||||
@override
|
||||
State<ClideFilterBox> createState() => _ClideFilterBoxState();
|
||||
}
|
||||
|
||||
class _ClideFilterBoxState extends State<ClideFilterBox> {
|
||||
final _controller = TextEditingController();
|
||||
final _focus = FocusNode();
|
||||
Timer? _debounceTimer;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounceTimer?.cancel();
|
||||
_controller.dispose();
|
||||
_focus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onChanged(String value) {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(widget.debounce, () => widget.onChanged(value));
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _clear() {
|
||||
_controller.clear();
|
||||
_debounceTimer?.cancel();
|
||||
widget.onChanged('');
|
||||
_focus.requestFocus();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final hasText = _controller.text.isNotEmpty;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
child: Semantics(
|
||||
label: widget.hint,
|
||||
textField: true,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: tokens.globalBorder),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
ClideIcon(PhosphorIcons.magnifyingGlass, size: 13, color: tokens.globalTextMuted),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: EditableText(
|
||||
controller: _controller,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(fontSize: clideFontCaption, color: tokens.globalForeground),
|
||||
cursorColor: tokens.globalFocus,
|
||||
backgroundCursorColor: tokens.globalTextMuted,
|
||||
maxLines: 1,
|
||||
onChanged: _onChanged,
|
||||
onSubmitted: widget.onSubmitted != null ? (v) => widget.onSubmitted!(v) : null,
|
||||
),
|
||||
),
|
||||
if (hasText)
|
||||
GestureDetector(
|
||||
onTap: _clear,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: ClideIcon(PhosphorIcons.xMark, size: 11, color: tokens.globalTextMuted),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ library;
|
||||
|
||||
export 'src/clide_button.dart';
|
||||
export 'src/clide_divider.dart';
|
||||
export 'src/clide_filter_box.dart';
|
||||
export 'src/clide_icon.dart';
|
||||
export 'src/clide_icon_rail.dart';
|
||||
export 'src/clide_palette.dart';
|
||||
|
||||
Reference in New Issue
Block a user