Skip to content

Commit

Permalink
Added Tab Pane component
Browse files Browse the repository at this point in the history
  • Loading branch information
sunarya-thito committed Dec 20, 2024
1 parent ae4e086 commit 7a8a20b
Show file tree
Hide file tree
Showing 11 changed files with 687 additions and 11 deletions.
8 changes: 7 additions & 1 deletion docs/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import 'package:docs/pages/docs/components/stepper_example.dart';
import 'package:docs/pages/docs/components/steps_example.dart';
import 'package:docs/pages/docs/components/switch_example.dart';
import 'package:docs/pages/docs/components/tab_list_example.dart';
import 'package:docs/pages/docs/components/tab_pane_example.dart';
import 'package:docs/pages/docs/components/table_example.dart';
import 'package:docs/pages/docs/components/tabs_example.dart';
import 'package:docs/pages/docs/components/text_area_example.dart';
Expand Down Expand Up @@ -112,7 +113,7 @@ void main() async {
double initialSurfaceOpacity = prefs.getDouble('surfaceOpacity') ?? 1.0;
double initialSurfaceBlur = prefs.getDouble('surfaceBlur') ?? 0.0;
runApp(MyApp(
initialColorScheme: initialColorScheme ?? colorSchemes['darkZinc']!,
initialColorScheme: initialColorScheme ?? colorSchemes['darkGreen']!,
initialRadius: initialRadius,
initialScaling: initialScaling,
initialSurfaceOpacity: initialSurfaceOpacity,
Expand Down Expand Up @@ -658,6 +659,11 @@ class MyAppState extends State<MyApp> {
builder: (context, state) => const TableExample(),
name: 'table',
),
GoRoute(
path: 'tab_pane',
builder: (context, state) => const TabPaneExample(),
name: 'tab_pane',
)
]),
]);
// ColorScheme colorScheme = ColorSchemes.darkZync();
Expand Down
101 changes: 101 additions & 0 deletions docs/lib/pages/docs/components/tab_pane/tab_pane_example_1.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import 'package:shadcn_flutter/shadcn_flutter.dart';

class TabPaneExample1 extends StatefulWidget {
const TabPaneExample1({super.key});

@override
State<TabPaneExample1> createState() => _TabPaneExample1State();
}

class _TabPaneExample1State extends State<TabPaneExample1> {
late List<TabItem> tabs;
int focused = 0;

@override
void didChangeDependencies() {
super.didChangeDependencies();
tabs = [
for (int i = 0; i < 3; i++) _buildTabItem(i),
];
}

TabItem _buildTabItem(int index) {
return TabItem(
key: ValueKey(index),
title: Text('Tab ${index + 1}'),
constraints: const BoxConstraints(minWidth: 150),
leading: OutlinedContainer(
backgroundColor: Colors.white,
width: 18,
height: 18,
borderRadius: Theme.of(context).borderRadiusMd,
child: Center(
child: Text(
(index + 1).toString(),
style: TextStyle(color: Colors.black),
).xSmall().bold(),
),
),
trailing: IconButton.ghost(
shape: ButtonShape.circle,
size: ButtonSize.xSmall,
icon: Icon(Icons.close),
onPressed: () {
setState(() {
tabs.removeWhere((element) => element.key == ValueKey(index));
});
},
),
);
}

@override
Widget build(BuildContext context) {
return TabPane(
tabs: tabs,
focused: tabs
.indexWhere((element) => (element.key as ValueKey).value == focused),
onFocused: (value) {
setState(() {
focused = (tabs[value].key as ValueKey).value;
});
},
onSort: (value) {
setState(() {
tabs = value;
});
},
leading: [
IconButton.secondary(
icon: Icon(Icons.arrow_drop_down),
size: ButtonSize.small,
density: ButtonDensity.iconDense,
onPressed: () {},
),
],
trailing: [
IconButton.ghost(
icon: Icon(Icons.add),
size: ButtonSize.small,
density: ButtonDensity.iconDense,
onPressed: () {
setState(() {
int max = tabs.fold<int>(0, (previousValue, element) {
return (element.key as ValueKey).value > previousValue
? (element.key as ValueKey).value
: previousValue;
});
tabs.add(_buildTabItem(max + 1));
});
},
)
],
child: Container(
child: Center(
child: Text('Tab ${focused + 1}').xLarge().bold(),
),
height: 400,
),
);
}
}
25 changes: 25 additions & 0 deletions docs/lib/pages/docs/components/tab_pane_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:docs/pages/docs/component_page.dart';
import 'package:docs/pages/docs/components/tab_pane/tab_pane_example_1.dart';
import 'package:docs/pages/widget_usage_example.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

class TabPaneExample extends StatelessWidget {
const TabPaneExample({super.key});

@override
Widget build(BuildContext context) {
return ComponentPage(
name: 'tab_pane',
description:
'A chrome-like tab pane that allows you to switch between different tabs.',
displayName: 'Tab Pane',
children: [
WidgetUsageExample(
title: 'Tab Pane Example',
child: TabPaneExample1(),
path: 'lib/pages/docs/components/tab_pane/tab_pane_example_1.dart',
),
],
);
}
}
10 changes: 5 additions & 5 deletions docs/lib/pages/docs_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,10 @@ class DocsPageState extends State<DocsPage> {
[
ShadcnDocsPage('Avatar', 'avatar'),
ShadcnDocsPage('Avatar Group', 'avatar_group'),
ShadcnDocsPage(
'Data Table', 'data_table', ShadcnFeatureTag.workInProgress),
// ShadcnDocsPage(
// 'Data Table', 'data_table', ShadcnFeatureTag.experimental),
// TODO also make it zoomable like: https://zoom-chart-demo.vercel.app/
ShadcnDocsPage('Chart', 'chart', ShadcnFeatureTag.workInProgress),
// ShadcnDocsPage('Chart', 'chart', ShadcnFeatureTag.workInProgress),
ShadcnDocsPage('Code Snippet', 'code_snippet'),
ShadcnDocsPage('Table', 'table', ShadcnFeatureTag.newFeature),
ShadcnDocsPage('Tracker', 'tracker'),
Expand Down Expand Up @@ -231,7 +231,7 @@ class DocsPageState extends State<DocsPage> {
ShadcnDocsPage('Form', 'form'),
// TODO: Image Input (with cropper and rotate tool, upload from file or take photo from camera)
// ShadcnDocsPage(
// 'Image Input', 'image_input', ShadcnFeatureTag.workInProgress),
// 'Image Input', 'image_input', ShadcnFeatureTag.workInProgress),
// replaced with File Input
ShadcnDocsPage('Text Input', 'input'),
// TODO: same as text input, but has dropdown autocomplete like chip input, the difference is, it does not convert
Expand Down Expand Up @@ -284,7 +284,7 @@ class DocsPageState extends State<DocsPage> {
ShadcnDocsPage('Tabs', 'tabs'),
ShadcnDocsPage('Tab List', 'tab_list'),
// TODO: like a chrome tab, complete with its view
ShadcnDocsPage('Tab Pane', 'tab_pane', ShadcnFeatureTag.workInProgress),
ShadcnDocsPage('Tab Pane', 'tab_pane', ShadcnFeatureTag.experimental),
ShadcnDocsPage('Tree', 'tree'),
// aka Bottom Navigation Bar
ShadcnDocsPage('Navigation Bar', 'navigation_bar'),
Expand Down
1 change: 1 addition & 0 deletions lib/shadcn_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export 'src/components/menu/popup.dart';
export 'src/components/navigation/navigation_bar.dart';
export 'src/components/navigation/pagination.dart';
export 'src/components/navigation/tab_list.dart';
export 'src/components/navigation/tab_pane.dart';
export 'src/components/navigation/tabs.dart';
export 'src/components/overlay/dialog.dart';
export 'src/components/overlay/drawer.dart';
Expand Down
120 changes: 120 additions & 0 deletions lib/src/components/display/fade_scroll.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import '../../../shadcn_flutter.dart';

class FadeScroll extends StatelessWidget {
final double startOffset;
final double endOffset;
final double startCrossOffset;
final double endCrossOffset;
final Widget child;
final ScrollController controller;
final List<Color> gradient;

const FadeScroll({
Key? key,
required this.startOffset,
required this.endOffset,
required this.child,
required this.controller,
required this.gradient,
this.startCrossOffset = 0,
this.endCrossOffset = 0,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
child: child,
builder: (context, child) {
if (!controller.hasClients) {
return child!;
}
final position = controller.position.pixels;
final max = controller.position.maxScrollExtent;
final min = controller.position.minScrollExtent;
final direction = controller.position.axis;
final size = controller.position.viewportDimension;
bool shouldFadeStart = position > min;
bool shouldFadeEnd = position < max;
if (!shouldFadeStart && !shouldFadeEnd) {
return child!;
}
return ShaderMask(
shaderCallback: (bounds) {
Alignment start = direction == Axis.horizontal
? Alignment.centerLeft
: Alignment.topCenter;
Alignment end = direction == Axis.horizontal
? Alignment.centerRight
: Alignment.bottomCenter;
double relativeStart = startOffset / size;
double relativeEnd = 1 - endOffset / size;
List<double> stops = shouldFadeStart && shouldFadeEnd
? [
for (int i = 0; i < gradient.length; i++)
(i / gradient.length) * relativeStart,
relativeStart,
relativeEnd,
for (int i = 1; i < gradient.length + 1; i++)
relativeEnd + (i / gradient.length) * (1 - relativeEnd),
]
: shouldFadeStart
? [
for (int i = 0; i < gradient.length; i++)
(i / gradient.length) * relativeStart,
relativeStart,
1
]
: [
0,
relativeEnd,
for (int i = 1; i < gradient.length + 1; i++)
relativeEnd +
(i / gradient.length) * (1 - relativeEnd),
];
return LinearGradient(
colors: [
if (shouldFadeStart) ...gradient,
Colors.white,
Colors.white,
if (shouldFadeEnd) ...gradient.reversed,
],
stops: stops,
begin: start,
end: end,
transform: const _ScaleGradient(Offset(1, 1.5)))
.createShader(bounds);
},
child: child!,
);
},
);
}
}

class _ScaleGradient extends GradientTransform {
final Offset scale;

const _ScaleGradient(this.scale);

@override
Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
final center = bounds.center;
final dx = center.dx * (1 - scale.dx);
final dy = center.dy * (1 - scale.dy);
return Matrix4.identity()
..translate(dx, dy)
..scale(scale.dx, scale.dy)
..translate(-dx, -dy);
}
}

Rect _inflateRect(Rect rect,
{double? left, double? top, double? right, double? bottom}) {
return Rect.fromLTRB(
rect.left - (left ?? 0),
rect.top - (top ?? 0),
rect.right + (right ?? 0),
rect.bottom + (bottom ?? 0),
);
}
18 changes: 16 additions & 2 deletions lib/src/components/layout/sortable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,9 @@ class _SortableState<T> extends State<Sortable<T>>
Offset minOffset = _session!.minOffset;
Offset maxOffset = _session!.maxOffset;
if (_session != null) {
Size size = _session!.layer.context.size!;
RenderBox sessionRenderBox =
_session!.layer.context.findRenderObject() as RenderBox;
Size size = sessionRenderBox.size;
if (_session!.lock) {
double minX = -minOffset.dx;
double maxX = size.width - maxOffset.dx;
Expand Down Expand Up @@ -668,9 +670,11 @@ class _SortableState<T> extends State<Sortable<T>>
class SortableLayer extends StatefulWidget {
final Widget child;
final bool lock;
final Clip? clipBehavior;
const SortableLayer({
super.key,
this.lock = false,
this.clipBehavior,
required this.child,
});

Expand All @@ -692,6 +696,9 @@ class _SortableLayerState extends State<SortableLayer> {
}

void removeDraggingSession(_SortableDraggingSession session) {
if (!mounted) {
return;
}
if (_sessions.value.contains(session)) {
setState(() {
_sessions.mutate((value) {
Expand All @@ -707,7 +714,8 @@ class _SortableLayerState extends State<SortableLayer> {
data: this,
child: Stack(
fit: StackFit.passthrough,
clipBehavior: widget.lock ? Clip.hardEdge : Clip.none,
clipBehavior:
widget.clipBehavior ?? (widget.lock ? Clip.hardEdge : Clip.none),
children: [
widget.child,
for (final session in _sessions.value)
Expand Down Expand Up @@ -764,6 +772,12 @@ class _ScrollableSortableLayerState extends State<ScrollableSortableLayer>
ticker = createTicker(_scroll);
}

@override
void dispose() {
ticker.dispose();
super.dispose();
}

_SortableState? _attached;
Offset? _globalPosition;
Duration? _lastElapsed;
Expand Down
10 changes: 7 additions & 3 deletions lib/src/components/layout/table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2138,13 +2138,17 @@ class RenderTableLayout extends RenderBox
}

// convert the column widths and row heights to a list, where missing values are 0
List<double> columnWidthsList =
List.filled(columnWidths.keys.reduce(max) + 1, 0);
List<double> columnWidthsList = List.generate(maxColumn + 1, (index) {
return columnWidths[index] ?? 0;
});
columnWidths.forEach((key, value) {
columnWidthsList[key] = value;
});
List<double> rowHeightsList =
List.filled(rowHeights.keys.reduce(max) + 1, 0);
// List.filled(rowHeights.keys.reduce(max) + 1, 0);
List.generate(maxRow + 1, (index) {
return rowHeights[index] ?? 0;
});
rowHeights.forEach((key, value) {
rowHeightsList[key] = value;
});
Expand Down
Loading

0 comments on commit 7a8a20b

Please sign in to comment.