Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.43.2

- **FIX**: Prevent accordion from clipping focus rings by adding `clipBehavior` parameter to `ShadAccordionItem` and `ShadAccordionTheme`.
- **CHORE**: Make `ShadSizeTransition` public to allow custom size transitions with configurable clip behavior.

## 0.43.1

- **FIX**: Improve `ShadPortal` scroll and resize handling.
Expand Down
23 changes: 22 additions & 1 deletion example/lib/pages/accordion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ final details = [
content:
"Yes. It's animated by default, but you can disable it if you prefer.",
),
(
title: 'Focus ring test (Issue #582)',
content: 'input_field', // Special marker to render input field
),
];

class AccordionPage extends StatefulWidget {
Expand All @@ -32,6 +36,7 @@ class AccordionPage extends StatefulWidget {
class _AccordionPageState extends State<AccordionPage> {
var type = ShadAccordionVariant.single;
var underlineTitle = true;
var clipContent = false; // false = Clip.none (fixed), true = Clip.hardEdge (old bug)

@override
Widget build(BuildContext context) {
Expand All @@ -41,7 +46,18 @@ class _AccordionPageState extends State<AccordionPage> {
value: detail,
title: Text(detail.title),
underlineTitleOnHover: underlineTitle,
child: Text(detail.content),
clipBehavior: clipContent ? Clip.hardEdge : Clip.none,
child: detail.content == 'input_field'
? const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Focus the input below to test that the focus ring '
'is not clipped:'),
SizedBox(height: 8),
ShadInput(placeholder: Text('Focus me')),
],
)
: Text(detail.content),
);
},
);
Expand All @@ -63,6 +79,11 @@ class _AccordionPageState extends State<AccordionPage> {
value: underlineTitle,
onChanged: (v) => setState(() => underlineTitle = v),
),
MyBoolProperty(
label: 'Clip content (old bug)',
value: clipContent,
onChanged: (v) => setState(() => clipContent = v),
),
],
children: [
ConstrainedBox(
Expand Down
12 changes: 12 additions & 0 deletions lib/src/components/accordion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class ShadAccordionItem<T> extends StatefulWidget {
this.duration,
this.focusNode,
this.effects,
this.clipBehavior,
});

/// {@template ShadAccordionItem.value}
Expand Down Expand Up @@ -315,6 +316,13 @@ class ShadAccordionItem<T> extends StatefulWidget {
/// {@endtemplate}
final List<Effect<dynamic>>? effects;

/// {@template ShadAccordionItem.clipBehavior}
/// The clip behavior of the size transition animation.
/// Defaults to [Clip.none] to prevent clipping of focus rings and other
/// content that extends beyond the widget's boundary.
/// {@endtemplate}
final Clip? clipBehavior;

@override
State<ShadAccordionItem<T>> createState() => _ShadAccordionItemState<T>();
}
Expand Down Expand Up @@ -435,6 +443,9 @@ class _ShadAccordionItemState<T> extends State<ShadAccordionItem<T>>
theme.accordionTheme.padding ??
const EdgeInsets.symmetric(vertical: 16);

final effectiveClipBehavior =
widget.clipBehavior ?? theme.accordionTheme.clipBehavior ?? Clip.none;

final effectiveEffects =
widget.effects ??
theme.accordionTheme.effects ??
Expand All @@ -453,6 +464,7 @@ class _ShadAccordionItemState<T> extends State<ShadAccordionItem<T>>
SizeEffect(
curve: effectiveCurve,
duration: effectiveDuration,
clipBehavior: effectiveClipBehavior,
),
];

Expand Down
4 changes: 4 additions & 0 deletions lib/src/theme/components/accordion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ShadAccordionTheme with _$ShadAccordionTheme {
this.duration,
this.maintainState,
this.effects,
this.clipBehavior,
}) : _canMerge = canMerge;

@ignore
Expand Down Expand Up @@ -53,6 +54,9 @@ class ShadAccordionTheme with _$ShadAccordionTheme {
/// {@macro ShadAccordionItem.effects}
final List<Effect<dynamic>>? effects;

/// {@macro ShadAccordionItem.clipBehavior}
final Clip? clipBehavior;

static ShadAccordionTheme? lerp(
ShadAccordionTheme? a,
ShadAccordionTheme? b,
Expand Down
8 changes: 7 additions & 1 deletion lib/src/theme/components/accordion.g.theme.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 51 additions & 1 deletion lib/src/utils/effects.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,30 @@ class SizeEffect extends Effect<double> {
super.curve,
double? begin,
double? end,
this.clipBehavior = Clip.hardEdge,
}) : super(
begin: begin ?? (end == null ? defaultValue : neutralValue),
end: end ?? neutralValue,
);

/// {@template SizeEffect.clipBehavior}
/// The clip behavior of the size transition.
/// Defaults to [Clip.hardEdge].
/// Set to [Clip.none] to prevent clipping of focus rings and other
/// content that extends beyond the widget's boundary.
/// {@endtemplate}
final Clip clipBehavior;

@override
Widget build(
BuildContext context,
Widget child,
AnimationController controller,
EffectEntry entry,
) {
return SizeTransition(
return ShadSizeTransition(
sizeFactor: buildAnimation(controller, entry),
clipBehavior: clipBehavior,
child: child,
);
}
Expand All @@ -33,6 +43,46 @@ class SizeEffect extends Effect<double> {
static const defaultValue = 0.0;
}

/// {@template ShadSizeTransition}
/// A custom size transition widget that supports [clipBehavior].
///
/// This is similar to Flutter's [SizeTransition] but with configurable
/// clip behavior to allow focus rings and other content to extend beyond
/// the widget's boundary during animation.
/// {@endtemplate}
class ShadSizeTransition extends AnimatedWidget {
/// {@macro ShadSizeTransition}
const ShadSizeTransition({
super.key,
required Animation<double> sizeFactor,
this.clipBehavior = Clip.hardEdge,
this.child,
}) : super(listenable: sizeFactor);

final Clip clipBehavior;
final Widget? child;

Animation<double> get sizeFactor => listenable as Animation<double>;

@override
Widget build(BuildContext context) {
final result = Align(
alignment: AlignmentDirectional.topStart,
heightFactor: sizeFactor.value,
child: child,
);

if (clipBehavior == Clip.none) {
return result;
}

return ClipRect(
clipBehavior: clipBehavior,
child: result,
);
}
}

@immutable
class PaddingEffect extends Effect<double> {
const PaddingEffect({
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: shadcn_ui
description: shadcn/ui ported in Flutter. Awesome UI components for Flutter, fully customizable.
version: 0.43.1
version: 0.43.2
homepage: https://flutter-shadcn-ui.mariuti.com
repository: https://github.com/nank1ro/flutter-shadcn-ui
documentation: https://flutter-shadcn-ui.mariuti.com
Expand Down