사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)
This commit is contained in:
314
lib/widgets/shadcn/shad_select.dart
Normal file
314
lib/widgets/shadcn/shad_select.dart
Normal file
@@ -0,0 +1,314 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
class SuperportShadSelect<T> extends StatefulWidget {
|
||||
final String label;
|
||||
final String? placeholder;
|
||||
final List<T> items;
|
||||
final T? value;
|
||||
final String Function(T) itemLabel;
|
||||
final ValueChanged<T?>? onChanged;
|
||||
final bool enabled;
|
||||
final bool searchable;
|
||||
final String? errorText;
|
||||
final String? helperText;
|
||||
final Widget? prefixIcon;
|
||||
final VoidCallback? onClear;
|
||||
final Future<List<T>> Function(String)? onSearch;
|
||||
final bool required;
|
||||
|
||||
const SuperportShadSelect({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.items,
|
||||
required this.itemLabel,
|
||||
this.placeholder,
|
||||
this.value,
|
||||
this.onChanged,
|
||||
this.enabled = true,
|
||||
this.searchable = false,
|
||||
this.errorText,
|
||||
this.helperText,
|
||||
this.prefixIcon,
|
||||
this.onClear,
|
||||
this.onSearch,
|
||||
this.required = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SuperportShadSelect<T>> createState() => _SuperportShadSelectState<T>();
|
||||
}
|
||||
|
||||
class _SuperportShadSelectState<T> extends State<SuperportShadSelect<T>> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
List<T> _filteredItems = [];
|
||||
bool _isSearching = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_filteredItems = widget.items;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(SuperportShadSelect<T> oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.items != widget.items) {
|
||||
_filteredItems = widget.items;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleSearch(String query) async {
|
||||
if (query.isEmpty) {
|
||||
setState(() {
|
||||
_filteredItems = widget.items;
|
||||
_isSearching = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isSearching = true;
|
||||
});
|
||||
|
||||
if (widget.onSearch != null) {
|
||||
final results = await widget.onSearch!(query);
|
||||
setState(() {
|
||||
_filteredItems = results;
|
||||
_isSearching = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_filteredItems = widget.items.where((item) {
|
||||
return widget.itemLabel(item)
|
||||
.toLowerCase()
|
||||
.contains(query.toLowerCase());
|
||||
}).toList();
|
||||
_isSearching = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.label,
|
||||
style: theme.textTheme.small.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
if (widget.required)
|
||||
Text(
|
||||
' *',
|
||||
style: theme.textTheme.small.copyWith(
|
||||
color: theme.colorScheme.destructive,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ShadSelect<T>(
|
||||
placeholder: widget.placeholder != null
|
||||
? Text(widget.placeholder!)
|
||||
: const Text('선택하세요'),
|
||||
selectedOptionBuilder: (context, value) {
|
||||
if (value == null) return const Text('선택하세요');
|
||||
return Text(widget.itemLabel(value));
|
||||
},
|
||||
enabled: widget.enabled,
|
||||
options: _filteredItems.map((item) {
|
||||
return ShadOption(
|
||||
value: item,
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.value == item)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.itemLabel(item),
|
||||
style: theme.textTheme.small.copyWith(
|
||||
fontWeight: widget.value == item
|
||||
? FontWeight.w600
|
||||
: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
if (widget.errorText != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.errorText!,
|
||||
style: theme.textTheme.small.copyWith(
|
||||
color: theme.colorScheme.destructive,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (widget.helperText != null && widget.errorText == null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.helperText!,
|
||||
style: theme.textTheme.small.copyWith(
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CascadeSelect<T, U> extends StatefulWidget {
|
||||
final String parentLabel;
|
||||
final String childLabel;
|
||||
final List<T> parentItems;
|
||||
final String Function(T) parentItemLabel;
|
||||
final Future<List<U>> Function(T) getChildItems;
|
||||
final String Function(U) childItemLabel;
|
||||
final T? parentValue;
|
||||
final U? childValue;
|
||||
final ValueChanged<T?>? onParentChanged;
|
||||
final ValueChanged<U?>? onChildChanged;
|
||||
final bool enabled;
|
||||
final bool searchable;
|
||||
final bool required;
|
||||
|
||||
const CascadeSelect({
|
||||
super.key,
|
||||
required this.parentLabel,
|
||||
required this.childLabel,
|
||||
required this.parentItems,
|
||||
required this.parentItemLabel,
|
||||
required this.getChildItems,
|
||||
required this.childItemLabel,
|
||||
this.parentValue,
|
||||
this.childValue,
|
||||
this.onParentChanged,
|
||||
this.onChildChanged,
|
||||
this.enabled = true,
|
||||
this.searchable = true,
|
||||
this.required = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CascadeSelect<T, U>> createState() => _CascadeSelectState<T, U>();
|
||||
}
|
||||
|
||||
class _CascadeSelectState<T, U> extends State<CascadeSelect<T, U>> {
|
||||
List<U> _childItems = [];
|
||||
bool _isLoadingChildren = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.parentValue != null) {
|
||||
_loadChildItems(widget.parentValue as T);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CascadeSelect<T, U> oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.parentValue != widget.parentValue &&
|
||||
widget.parentValue != null) {
|
||||
_loadChildItems(widget.parentValue as T);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadChildItems(T parentValue) async {
|
||||
setState(() {
|
||||
_isLoadingChildren = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final items = await widget.getChildItems(parentValue);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_childItems = items;
|
||||
_isLoadingChildren = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_childItems = [];
|
||||
_isLoadingChildren = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
SuperportShadSelect<T>(
|
||||
label: widget.parentLabel,
|
||||
items: widget.parentItems,
|
||||
itemLabel: widget.parentItemLabel,
|
||||
value: widget.parentValue,
|
||||
onChanged: (value) {
|
||||
widget.onParentChanged?.call(value);
|
||||
widget.onChildChanged?.call(null);
|
||||
if (value != null) {
|
||||
_loadChildItems(value);
|
||||
} else {
|
||||
setState(() {
|
||||
_childItems = [];
|
||||
});
|
||||
}
|
||||
},
|
||||
enabled: widget.enabled,
|
||||
searchable: widget.searchable,
|
||||
required: widget.required,
|
||||
placeholder: '${widget.parentLabel}을(를) 선택하세요',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SuperportShadSelect<U>(
|
||||
label: widget.childLabel,
|
||||
items: _childItems,
|
||||
itemLabel: widget.childItemLabel,
|
||||
value: widget.childValue,
|
||||
onChanged: widget.onChildChanged,
|
||||
enabled: widget.enabled &&
|
||||
widget.parentValue != null &&
|
||||
!_isLoadingChildren,
|
||||
searchable: widget.searchable,
|
||||
required: widget.required,
|
||||
placeholder: _isLoadingChildren
|
||||
? '로딩 중...'
|
||||
: widget.parentValue == null
|
||||
? '먼저 ${widget.parentLabel}을(를) 선택하세요'
|
||||
: '${widget.childLabel}을(를) 선택하세요',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user