nank1ro / flutter-shadcn-ui

shadcn-ui ported in Flutter. Awesome UI components for Flutter, fully customizable.
https://mariuti.com/shadcn-ui
MIT License
657 stars 42 forks source link

ShadTable width problems on Flutter Web using 'RemainingTableSpanExtent' #61

Closed louis-sicko closed 1 month ago

louis-sicko commented 1 month ago

Hello,

I implemented the ShadTable into a prototype project and experiencing an weird behaviour with the "RemainingTableSpanExtent".

On mobile the middle column of the table expands perfectly fine.

image

On web I experience the following: image

Code:

import 'package:prototype_app/components/appbar.dart';
import 'package:prototype_app/components/timesheet_card.dart';
import 'package:prototype_app/theme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:shadcn_ui/shadcn_ui.dart';

var timesheets = [
  (
    date: DateTime.now(),
    startTime: "7:00",
    endTime: "16:00",
    timeBreak: "1:00",
  ),
  (
    date: DateTime.now(),
    startTime: "7:00",
    endTime: "16:00",
    timeBreak: "1:00",
  ),
  (
    date: DateTime.now(),
    startTime: "7:00",
    endTime: "16:00",
    timeBreak: "1:00",
  ),
  (
    date: DateTime.now(),
    startTime: "7:00",
    endTime: "16:00",
    timeBreak: "1:00",
  ),
];

final months = {
  'jan': 'Januar',
  'feb': 'Februrar',
  'mar': 'März',
  'apr': 'April',
  'may': 'Mai',
  'jun': 'Juni',
  'jul': 'July',
  'aug': 'August',
  'sep': 'September',
  'okt': 'Oktober',
  'nov': 'November',
  'dez': 'Dezember',
};

final years = {
  2024,
  2023,
  2022,
};

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

  @override
  Widget build(BuildContext context) {
    final theme = ShadTheme.of(context);

    return Scaffold(
      appBar: AppBar(context, false),
      body: Padding(
        padding: const EdgeInsets.only(
          top: 20,
          left: 20,
          right: 20,
        ),
        child: SafeArea(
          child: Wrap(
            runSpacing: 15,
            children: [
              const TimesheetCard(
                overviewMode: false,
              ),
              Row(
                children: [
                  Expanded(
                    child: Padding(
                      padding: const EdgeInsets.only(right: 8.0),
                      child: ShadCard(
                        content: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisAlignment: MainAxisAlignment.start,
                          children: [
                            Text(
                              "Überstunden",
                              style: theme.textTheme.large,
                            ),
                            Text(
                              "3:54",
                              style: theme.textTheme.h2,
                            ),
                            Text(
                              "2024",
                              style: theme.textTheme.p,
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
                  Expanded(
                    child: Padding(
                      padding: const EdgeInsets.only(left: 8.0),
                      child: ShadCard(
                        content: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisAlignment: MainAxisAlignment.start,
                          children: [
                            Text(
                              "Vertrag",
                              style: theme.textTheme.large,
                            ),
                            Text(
                              "40:00",
                              style: theme.textTheme.h2,
                            ),
                            Text(
                              "Noch 13:00",
                              style: theme.textTheme.p,
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
                ],
              ),
              Padding(
                padding: const EdgeInsets.only(top: 12),
                child: Text("Bereits erfasst", style: theme.textTheme.h3),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Expanded(
                    child: ShadSelect<String>(
                      placeholder: const Text('Monat'),
                      options: [
                        Padding(
                          padding: const EdgeInsets.fromLTRB(32, 6, 6, 6),
                          child: Text(
                            'Monate',
                            style: theme.textTheme.muted.copyWith(
                              fontWeight: FontWeight.w600,
                              color: theme.colorScheme.popoverForeground,
                            ),
                            textAlign: TextAlign.start,
                          ),
                        ),
                        ...months.entries.map((e) =>
                            ShadOption(value: e.key, child: Text(e.value))),
                      ],
                      selectedOptionBuilder: (context, value) =>
                          Text(months[value]!),
                    ),
                  ),
                  Expanded(
                    child: ShadSelect<String>(
                      placeholder: const Text('Jahr'),
                      options: [
                        Padding(
                          padding: const EdgeInsets.fromLTRB(32, 6, 6, 6),
                          child: Text(
                            'Jahre',
                            style: theme.textTheme.muted.copyWith(
                              fontWeight: FontWeight.w600,
                              color: theme.colorScheme.popoverForeground,
                            ),
                            textAlign: TextAlign.start,
                          ),
                        ),
                        ...years.map((e) => ShadOption(
                            value: e.toString(), child: Text(e.toString()))),
                      ],
                      selectedOptionBuilder: (context, value) => Text(value),
                    ),
                  ),
                ],
              ),
              Center(
                child: ConstrainedBox(
                  constraints: const BoxConstraints(
                    maxWidth: 600,
                    // added just to center the table vertically
                    maxHeight: 700,
                  ),
                  child: ShadTable.list(
                    columnSpanExtent: (index) {
                      if (index == 0) return const FixedTableSpanExtent(80);
                      if (index == 1) {
                        return const MinTableSpanExtent(
                          FixedTableSpanExtent(150),
                          RemainingTableSpanExtent(),
                        );
                      }
                      return const FixedTableSpanExtent(130);
                    },
                    rowSpanExtent: (row) {
                      return const FixedTableSpanExtent(60);
                    },
                    children: timesheets.map(
                      (timesheet) => [
                        ShadTableCell(
                          alignment: Alignment.center,
                          child: Wrap(
                            crossAxisAlignment: WrapCrossAlignment.center,
                            direction: Axis.vertical,
                            children: [
                              Text(
                                "${timesheet.date.day}",
                                textAlign: TextAlign.center,
                                style: theme.textTheme.large,
                              ),
                              Text(
                                DateFormat('EE', 'de_DE')
                                    .format(timesheet.date)
                                    .replaceAll(".", ""),
                                textAlign: TextAlign.center,
                                style: theme.textTheme.p
                                    .copyWith(color: primaryColor),
                              ),
                            ],
                          ),
                        ),
                        ShadTableCell(
                          child: Wrap(
                            direction: Axis.vertical,
                            children: [
                              Text(
                                "${timesheet.startTime} - ${timesheet.endTime}",
                                style: theme.textTheme.large,
                              ),
                              Text(
                                "Pause: ${timesheet.timeBreak}",
                                style: theme.textTheme.p,
                              ),
                            ],
                          ),
                        ),
                        const ShadTableCell(
                            alignment: Alignment.center,
                            child: Row(
                              crossAxisAlignment: CrossAxisAlignment.center,
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                ShadButton.secondary(
                                  padding: EdgeInsets.symmetric(horizontal: 8),
                                  icon: Icon(Icons.edit),
                                ),
                                ShadButton.destructive(
                                  padding: EdgeInsets.symmetric(horizontal: 8),
                                  icon: Icon(Icons.delete),
                                ),
                              ],
                            )),
                      ],
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
nank1ro commented 1 month ago

Can you try pointing to the latest main? I added the scroll physics to disable the scroll horizontally and vertically if needed. I tried with:

ShadTable.list(
  horizontalScrollPhysics:
      const NeverScrollableScrollPhysics(),
  columnSpanExtent: (index) {
    if (index == 0) {
      return const FractionalTableSpanExtent(0.2);
    }
    if (index == 1) {
      return const FractionalTableSpanExtent(0.6);
    }
    return const FixedTableSpanExtent(130);
  },
  rowSpanExtent: (row) {
    return const FixedTableSpanExtent(60);
  },
  ...
),

On mobile you may still use the old extents. Use a way to determine if on web or mobile, or by using ShadResponsiveBuilder or by using a combination of MinTableSpanExtent and MaxTableSpanExtent

nank1ro commented 1 month ago

The FractionalTableSpanExtent should use the entire size available. So it should work even on mobile if the total reaches 1.0. I'm trying to understand why it doesn't work

nank1ro commented 1 month ago

This one seems working pretty fine on large sizes and even on small ones

 return FractionalTableSpanExtent(
                        index == 0
                            ? 0.1
                            : index == 1
                                ? 0.5
                                : 0.4,
                      );

Still overflows when the screen is to small but can be adjusted accordingly. Let me know if you have any other problem or if I can close the issue

nank1ro commented 1 month ago

Closing because the problem mentioned is not a bug. Feel free to open another issue if you find again the problem