feat: add quick edit panel (#4089)

* feat: add quick edit panel

* feat: improve datepicker color

* fix: quick edit field editor overflow

* chore: try to fix mobile ci

* Revert "chore: try to fix mobile ci"

This reverts commit 68f0ccecd6.
This commit is contained in:
Lucas.Xu 2023-12-05 17:34:42 +08:00 committed by GitHub
parent d25830aece
commit 7d55153475
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 371 additions and 101 deletions

View File

@ -2,24 +2,51 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class AppBarBackButton extends StatelessWidget {
const AppBarBackButton({
super.key,
required this.onTap,
this.onTap,
});
final VoidCallback onTap;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return AppBarButton(
onTap: onTap,
onTap: onTap ?? () => context.pop(),
child: const Icon(Icons.arrow_back_ios_new),
);
}
}
class AppBarCloseButton extends StatelessWidget {
const AppBarCloseButton({
super.key,
this.onTap,
this.margin = const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 12.0,
),
});
final VoidCallback? onTap;
final EdgeInsets margin;
@override
Widget build(BuildContext context) {
return FlowyButton(
useIntrinsicWidth: true,
text: const Icon(
Icons.close,
),
margin: margin,
onTap: onTap ?? () => context.pop(),
);
}
}
class AppBarCancelButton extends StatelessWidget {
const AppBarCancelButton({
super.key,

View File

@ -126,9 +126,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
),
],
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
actions: actions,
),
body: SafeArea(

View File

@ -1,3 +1,4 @@
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
@ -15,6 +16,8 @@ Future<T?> showMobileBottomSheet<T>(
bool showCloseButton = false,
String title = '', // only works if showHeader is true
Color? backgroundColor,
bool isScrollControlled = true,
BoxConstraints? constraints,
}) async {
assert(() {
if (showCloseButton || title.isNotEmpty) assert(showHeader);
@ -23,11 +26,12 @@ Future<T?> showMobileBottomSheet<T>(
return showModalBottomSheet<T>(
context: context,
isScrollControlled: true,
isScrollControlled: isScrollControlled,
enableDrag: isDragEnabled,
useSafeArea: true,
clipBehavior: Clip.antiAlias,
backgroundColor: backgroundColor,
constraints: constraints,
shape: shape ??
const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
@ -53,14 +57,8 @@ Future<T?> showMobileBottomSheet<T>(
showCloseButton
? Padding(
padding: EdgeInsets.only(left: padding.left),
child: FlowyButton(
useIntrinsicWidth: true,
text: const Icon(
Icons.close,
size: 24,
),
child: const AppBarCloseButton(
margin: EdgeInsets.zero,
onTap: () => Navigator.of(context).pop(),
),
)
: const SizedBox.shrink(),

View File

@ -1,8 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options_eidtor.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -13,18 +12,15 @@ class MobileEditPropertyScreen extends StatefulWidget {
static const routeName = '/edit_property';
static const argViewId = 'view_id';
static const argField = 'field';
static const argIsPrimary = 'is_primary';
const MobileEditPropertyScreen({
super.key,
required this.viewId,
required this.field,
this.isPrimary = false,
});
final String viewId;
final FieldPB field;
final bool isPrimary;
@override
State<MobileEditPropertyScreen> createState() =>
@ -79,29 +75,25 @@ class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
}
return FieldOptionEditor(
mode: FieldOptionMode.edit,
isPrimary: widget.isPrimary,
isPrimary: widget.field.isPrimary,
defaultValues: optionValues,
onOptionValuesChanged: (optionValues) {
this.optionValues = optionValues;
},
onAction: (action) {
final service = FieldBackendService(
final service = FieldServices(
viewId: viewId,
fieldId: fieldId,
);
switch (action) {
case FieldOptionAction.delete:
service.deleteField();
service.delete();
break;
case FieldOptionAction.duplicate:
service.duplicateField();
service.duplicate();
break;
case FieldOptionAction.hide:
FieldSettingsBackendService(viewId: viewId)
.updateFieldSettings(
fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysHidden,
);
service.hide();
break;
}
context.pop();

View File

@ -6,6 +6,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/option_color_list.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/number_format_bloc.dart';
@ -190,7 +191,7 @@ class _FieldOptionEditorState extends State<FieldOptionEditor> {
child: Column(
children: [
const _Divider(),
_OptionTextField(
OptionTextField(
controller: controller,
type: values.type,
onTextChanged: (value) {
@ -289,20 +290,24 @@ class _FieldOptionEditorState extends State<FieldOptionEditor> {
leftIcon: const FlowySvg(FlowySvgs.hide_s),
onTap: () => widget.onAction?.call(FieldOptionAction.hide),
),
if (!widget.isPrimary)
if (!widget.isPrimary) ...[
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_duplicate.tr(),
leftIcon: const FlowySvg(FlowySvgs.copy_s),
onTap: () => widget.onAction?.call(FieldOptionAction.duplicate),
),
if (!widget.isPrimary)
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_delete.tr(),
leftIcon: const FlowySvg(FlowySvgs.delete_s),
textColor: Theme.of(context).colorScheme.error,
leftIcon: FlowySvg(
FlowySvgs.delete_s,
color: Theme.of(context).colorScheme.error,
),
onTap: () => widget.onAction?.call(FieldOptionAction.delete),
),
],
]
};
}
@ -338,35 +343,6 @@ class _FieldOptionEditorState extends State<FieldOptionEditor> {
}
}
class _OptionTextField extends StatelessWidget {
const _OptionTextField({
required this.controller,
required this.type,
required this.onTextChanged,
});
final TextEditingController controller;
final FieldType type;
final void Function(String value) onTextChanged;
@override
Widget build(BuildContext context) {
return FlowyOptionTile.textField(
controller: controller,
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
onTextChanged: onTextChanged,
leftIcon: Padding(
padding: const EdgeInsets.only(left: 16.0),
child: FlowySvg(
type.svgData,
size: const Size.square(36.0),
blendMode: null,
),
),
);
}
}
class _PropertyType extends StatelessWidget {
const _PropertyType({
required this.type,

View File

@ -0,0 +1,35 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flutter/material.dart';
class OptionTextField extends StatelessWidget {
const OptionTextField({
super.key,
required this.controller,
required this.type,
required this.onTextChanged,
});
final TextEditingController controller;
final FieldType type;
final void Function(String value) onTextChanged;
@override
Widget build(BuildContext context) {
return FlowyOptionTile.textField(
controller: controller,
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
onTextChanged: onTextChanged,
leftIcon: Padding(
padding: const EdgeInsets.only(left: 16.0),
child: FlowySvg(
type.svgData,
size: const Size.square(36.0),
blendMode: null,
),
),
);
}
}

View File

@ -1,3 +1,4 @@
export 'mobile_field_name_text_field.dart';
export 'mobile_create_field_button.dart';
export 'mobile_field_name_text_field.dart';
export 'mobile_row_property_list.dart';
export 'option_text_field.dart';

View File

@ -77,15 +77,17 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
builder: (_, controller) => Material(
child: ColoredBox(
color: Theme.of(context).colorScheme.surface,
child: SingleChildScrollView(
controller: controller,
child: Column(
children: [
const DragHandler(),
_buildHeader(),
_buildBody(),
],
),
child: Column(
children: [
const DragHandler(),
_buildHeader(),
Expanded(
child: SingleChildScrollView(
controller: controller,
child: _buildBody(),
),
),
],
),
),
),

View File

@ -3,6 +3,7 @@ import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_cr
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_edit_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options_eidtor.dart';
import 'package:appflowy/mobile/presentation/database/field/quick_edit_field_bottom_sheet.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:flutter/material.dart';
@ -43,7 +44,7 @@ void showCreateFieldBottomSheet(BuildContext context, String viewId) {
);
}
void showEditFieldScreen(
Future<FieldOptionValues?> showEditFieldScreen(
BuildContext context,
String viewId,
FieldInfo field,
@ -53,7 +54,6 @@ void showEditFieldScreen(
extra: {
MobileEditPropertyScreen.argViewId: viewId,
MobileEditPropertyScreen.argField: field.field,
MobileEditPropertyScreen.argIsPrimary: field.isPrimary,
},
);
if (optionValues != null) {
@ -79,4 +79,30 @@ void showEditFieldScreen(
);
}
}
return optionValues;
}
void showQuickEditField(
BuildContext context,
String viewId,
FieldInfo fieldInfo,
) async {
showMobileBottomSheet(
context,
padding: EdgeInsets.zero,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
resizeToAvoidBottomInset: true,
builder: (context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 500),
child: SingleChildScrollView(
child: QuickEditField(
viewId: viewId,
fieldInfo: fieldInfo,
),
),
);
},
);
}

View File

@ -0,0 +1,148 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class QuickEditField extends StatefulWidget {
const QuickEditField({
super.key,
required this.viewId,
required this.fieldInfo,
});
final String viewId;
final FieldInfo fieldInfo;
@override
State<QuickEditField> createState() => _QuickEditFieldState();
}
class _QuickEditFieldState extends State<QuickEditField> {
final TextEditingController controller = TextEditingController();
late final FieldServices service = FieldServices(
viewId: widget.viewId,
fieldId: widget.fieldInfo.field.id,
);
late FieldType fieldType;
@override
void initState() {
super.initState();
fieldType = widget.fieldInfo.fieldType;
controller.text = widget.fieldInfo.field.name;
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const AppBarCloseButton(),
OptionTextField(
controller: controller,
type: fieldType,
onTextChanged: (text) async {
await service.updateName(text);
},
),
const _Divider(),
FlowyOptionTile.text(
text: LocaleKeys.grid_field_editProperty.tr(),
leftIcon: const FlowySvg(FlowySvgs.edit_s),
onTap: () async {
final optionValues = await showEditFieldScreen(
context,
widget.viewId,
widget.fieldInfo,
);
if (optionValues != null) {
setState(() {
fieldType = optionValues.type;
controller.text = optionValues.name;
});
}
},
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.grid_field_hide.tr(),
leftIcon: const FlowySvg(FlowySvgs.hide_s),
onTap: () async {
context.pop();
await service.hide();
},
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.grid_field_insertLeft.tr(),
leftIcon: const FlowySvg(FlowySvgs.insert_left_s),
onTap: () async {
context.pop();
await service.insertLeft();
},
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.grid_field_insertRight.tr(),
leftIcon: const FlowySvg(FlowySvgs.insert_right_s),
onTap: () async {
context.pop();
await service.insertRight();
},
),
if (!widget.fieldInfo.isPrimary) ...[
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_duplicate.tr(),
leftIcon: const FlowySvg(FlowySvgs.copy_s),
onTap: () async {
context.pop();
await service.duplicate();
},
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
leftIcon: FlowySvg(
FlowySvgs.delete_s,
color: Theme.of(context).colorScheme.error,
),
onTap: () async {
context.pop();
await service.delete();
},
),
],
],
);
}
}
class _Divider extends StatelessWidget {
const _Divider();
@override
Widget build(BuildContext context) {
return const VSpace(20);
}
}

View File

@ -54,9 +54,7 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
LocaleKeys.titleBar_font.tr(),
fontSize: 14.0,
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
),
body: SafeArea(
child: ListView.separated(

View File

@ -39,9 +39,7 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
LocaleKeys.titleBar_language.tr(),
fontSize: 14.0,
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
),
body: SafeArea(
child: ListView.separated(

View File

@ -17,6 +17,7 @@ class FlowyOptionTile extends StatelessWidget {
this.showTopBorder = true,
this.showBottomBorder = true,
this.text,
this.textColor,
this.controller,
this.leading,
this.onTap,
@ -33,6 +34,7 @@ class FlowyOptionTile extends StatelessWidget {
factory FlowyOptionTile.text({
required String text,
Color? textColor,
bool showTopBorder = true,
bool showBottomBorder = true,
Widget? leftIcon,
@ -42,6 +44,7 @@ class FlowyOptionTile extends StatelessWidget {
return FlowyOptionTile._(
type: FlowyOptionTileType.text,
text: text,
textColor: textColor,
controller: null,
onTap: onTap,
showTopBorder: showTopBorder,
@ -128,6 +131,7 @@ class FlowyOptionTile extends StatelessWidget {
final bool showTopBorder;
final bool showBottomBorder;
final String? text;
final Color? textColor;
final TextEditingController? controller;
final EdgeInsets textFieldPadding;
final void Function()? onTap;
@ -156,7 +160,7 @@ class FlowyOptionTile extends StatelessWidget {
children: [
_buildText(),
..._buildTextField(),
const Spacer(),
if (controller == null) const Spacer(),
trailing ?? const SizedBox.shrink(),
const HSpace(12.0),
],
@ -188,6 +192,7 @@ class FlowyOptionTile extends StatelessWidget {
useIntrinsicWidth: true,
text: FlowyText(
text!,
color: textColor,
),
margin: const EdgeInsets.symmetric(
horizontal: 16.0,
@ -226,7 +231,6 @@ class FlowyOptionTile extends StatelessWidget {
child: Container(
constraints: const BoxConstraints.tightFor(
height: 54.0,
width: double.infinity,
),
alignment: Alignment.center,
child: TextField(

View File

@ -26,9 +26,7 @@ class MobileColorPickerScreen extends StatelessWidget {
title ?? LocaleKeys.titleBar_pageIcon.tr(),
fontSize: 14.0,
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
),
body: SafeArea(
child: FlowyMobileColorPicker(

View File

@ -4,7 +4,6 @@ import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class IconPickerPage extends StatelessWidget {
const IconPickerPage({
@ -25,9 +24,7 @@ class IconPickerPage extends StatelessWidget {
title ?? LocaleKeys.titleBar_pageIcon.tr(),
fontSize: 14.0,
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
),
body: SafeArea(
child: FlowyIconPicker(

View File

@ -0,0 +1,69 @@
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
// This class is used for combining the
// 1. FieldBackendService
// 2. FieldSettingsBackendService
// 3. TypeOptionBackendService
//
// including,
// hide, delete, duplicated,
// insertLeft, insertRight,
// updateName
class FieldServices {
FieldServices({
required this.viewId,
required this.fieldId,
}) : fieldBackendService = FieldBackendService(
viewId: viewId,
fieldId: fieldId,
),
fieldSettingsService = FieldSettingsBackendService(
viewId: viewId,
);
final String viewId;
final String fieldId;
final FieldBackendService fieldBackendService;
final FieldSettingsBackendService fieldSettingsService;
Future<void> hide() async {
await fieldSettingsService.updateFieldSettings(
fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysHidden,
);
}
Future<void> delete() async {
await fieldBackendService.deleteField();
}
Future<void> duplicate() async {
await fieldBackendService.duplicateField();
}
Future<void> insertLeft() async {
await TypeOptionBackendService.createFieldTypeOption(
viewId: viewId,
position: CreateFieldPosition.Before,
targetFieldId: fieldId,
);
}
Future<void> insertRight() async {
await TypeOptionBackendService.createFieldTypeOption(
viewId: viewId,
position: CreateFieldPosition.After,
targetFieldId: fieldId,
);
}
Future<void> updateName(String name) async {
await fieldBackendService.updateField(
name: name,
);
}
}

View File

@ -30,7 +30,7 @@ class MobileFieldButton extends StatelessWidget {
return SizedBox(
width: fieldInfo.fieldSettings!.width.toDouble(),
child: FlowyButton(
onTap: () => showEditFieldScreen(context, viewId, fieldInfo),
onTap: () => showQuickEditField(context, viewId, fieldInfo),
radius: BorderRadius.zero,
margin: const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
leftIconSize: const Size.square(18),

View File

@ -165,6 +165,8 @@ class _DateCellState extends GridCellState<GridDateCell> {
showMobileBottomSheet(
context,
padding: EdgeInsets.zero,
backgroundColor:
Theme.of(context).colorScheme.secondaryContainer,
builder: (context) {
return MobileDateCellEditScreen(
controller: widget.cellControllerBuilder.build()

View File

@ -23,9 +23,7 @@ class MobileCodeLanguagePickerScreen extends StatelessWidget {
LocaleKeys.titleBar_language.tr(),
fontSize: 14.0,
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
),
body: SafeArea(
child: ListView.separated(

View File

@ -4,7 +4,6 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class ImagePickerPage extends StatefulWidget {
const ImagePickerPage({
@ -28,9 +27,7 @@ class _ImagePickerPageState extends State<ImagePickerPage> {
LocaleKeys.titleBar_pageIcon.tr(),
fontSize: 14.0,
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
),
body: SafeArea(
child: UploadImageMenu(

View File

@ -4,7 +4,6 @@ import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
enum MobileBlockActionType {
delete,
@ -74,9 +73,7 @@ class MobileBlockSettingsScreen extends StatelessWidget {
LocaleKeys.titleBar_actions.tr(),
fontSize: 14.0,
),
leading: AppBarBackButton(
onTap: () => context.pop(),
),
leading: const AppBarBackButton(),
),
body: SafeArea(
child: ListView.separated(

View File

@ -386,7 +386,6 @@ GoRoute _mobileEditPropertyPageRoute() {
child: MobileEditPropertyScreen(
viewId: args[MobileEditPropertyScreen.argViewId],
field: args[MobileEditPropertyScreen.argField],
isPrimary: args[MobileEditPropertyScreen.argIsPrimary],
),
);
},

View File

@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.75 14.7222L3.75 5" stroke="#333333" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.875 5.625L7.5 10L11.875 14.375" stroke="#333333" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 10L16.25 10" stroke="#333333" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 464 B

View File

@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.25 14.7222L16.25 5" stroke="#333333" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.125 5.625L12.5 10L8.125 14.375" stroke="#333333" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5 10L3.75 10" stroke="#333333" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 465 B