From 9cd552a1a7b9eefb0b5b8473de490bd302246bbf Mon Sep 17 00:00:00 2001 From: rem42 Date: Fri, 6 Nov 2026 15:53:24 +0100 Subject: [PATCH 1/6] feat: add shadcn admin ui --- .github/workflows/ci.yaml | 16 + .github/workflows/split_packages.yaml | 3 + .gitignore | 1 + composer.json | 4 + .../assets/shadcn-admin-ui-dashboard.png | Bin 0 -> 76350 bytes docs/.gitbook/assets/shadcn-admin-ui-form.png | Bin 0 -> 46808 bytes docs/.gitbook/assets/shadcn-admin-ui-list.png | Bin 0 -> 117286 bytes .../.gitbook/assets/shadcn-admin-ui-login.png | Bin 0 -> 22193 bytes docs/README.md | 1 + docs/SUMMARY.md | 10 + docs/shadcn-admin-ui/components.md | 210 +++ docs/shadcn-admin-ui/configuration.md | 91 + docs/shadcn-admin-ui/getting-started.md | 188 ++ docs/shadcn-admin-ui/grids.md | 70 + docs/shadcn-admin-ui/menu.md | 144 ++ docs/shadcn-admin-ui/migration.md | 62 + docs/shadcn-admin-ui/theming.md | 103 + ecs.php | 1 + src/ShadcnAdminUi/.gitattributes | 4 + src/ShadcnAdminUi/.gitignore | 2 + src/ShadcnAdminUi/LICENSE | 19 + .../LICENSE_OF_TRADEMARK_AND_LOGO | 162 ++ src/ShadcnAdminUi/README.md | 3 + .../admin/copy_to_clipboard_controller.js | 75 + .../admin/dismissible_controller.js | 8 + .../admin/grid_selection_controller.js | 63 + .../admin/menu_search_controller.js | 92 + .../admin/sidebar_trigger_controller.js | 16 + .../controllers/admin/tabs_controller.js | 111 ++ .../admin/theme_switcher_controller.js | 26 + .../shadcn/alert_dialog_controller.js | 53 + .../shadcn/collapsible_controller.js | 43 + .../controllers/shadcn/dialog_controller.js | 110 ++ .../shadcn/dropdown_menu_controller.js | 211 +++ .../shadcn/dropdown_menu_sub_controller.js | 59 + .../shadcn/input_group_controller.js | 11 + .../shadcn/input_group_display_controller.js | 30 + .../controllers/shadcn/sidebar_controller.js | 191 ++ .../controllers/shadcn/tooltip_controller.js | 184 ++ src/ShadcnAdminUi/assets/styles/theme.css | 479 +++++ .../assets/styles/vendor/shadcn.css | 638 +++++++ .../assets/styles/vendor/tw-animate.css | 2 + src/ShadcnAdminUi/composer.json | 88 + src/ShadcnAdminUi/config/app.php | 18 + .../config/app/grid/templates.php | 40 + .../config/app/twig_extra/twig_ux.php | 27 + .../config/app/twig_hooks/common/create.php | 111 ++ .../config/app/twig_hooks/common/index.php | 108 ++ .../config/app/twig_hooks/common/show.php | 75 + .../config/app/twig_hooks/common/update.php | 113 ++ .../config/app/twig_hooks/dashboard/index.php | 63 + .../config/app/twig_hooks/layout.php | 54 + .../config/app/twig_hooks/layout/navbar.php | 42 + .../config/app/twig_hooks/layout/sidebar.php | 39 + .../config/app/twig_hooks/security/login.php | 71 + src/ShadcnAdminUi/config/services.php | 18 + .../config/services/twig/component.php | 32 + src/ShadcnAdminUi/phpunit.xml.dist | 26 + .../src/Symfony/SyliusShadcnAdminUiBundle.php | 147 ++ .../Twig/Component/UserDropdownComponent.php | 57 + .../Extension/UxTwoCompatibilityExtension.php | 129 ++ .../components/shadcn/Alert.html.twig | 24 + .../components/shadcn/Alert/Action.html.twig | 9 + .../shadcn/Alert/Description.html.twig | 9 + .../components/shadcn/Alert/Title.html.twig | 9 + .../components/shadcn/AlertDialog.html.twig | 22 + .../shadcn/AlertDialog/Action.html.twig | 10 + .../shadcn/AlertDialog/Cancel.html.twig | 10 + .../shadcn/AlertDialog/Content.html.twig | 22 + .../shadcn/AlertDialog/Description.html.twig | 9 + .../shadcn/AlertDialog/Footer.html.twig | 9 + .../shadcn/AlertDialog/Header.html.twig | 9 + .../shadcn/AlertDialog/Media.html.twig | 9 + .../shadcn/AlertDialog/Title.html.twig | 9 + .../shadcn/AlertDialog/Trigger.html.twig | 8 + .../components/shadcn/Avatar.html.twig | 15 + .../components/shadcn/Avatar/Badge.html.twig | 9 + .../shadcn/Avatar/Fallback.html.twig | 9 + .../components/shadcn/Avatar/Group.html.twig | 9 + .../shadcn/Avatar/GroupCount.html.twig | 9 + .../components/shadcn/Avatar/Image.html.twig | 8 + .../components/shadcn/Badge.html.twig | 29 + .../components/shadcn/Breadcrumb.html.twig | 7 + .../shadcn/Breadcrumb/Ellipsis.html.twig | 11 + .../shadcn/Breadcrumb/Item.html.twig | 9 + .../shadcn/Breadcrumb/Link.html.twig | 9 + .../shadcn/Breadcrumb/List.html.twig | 9 + .../shadcn/Breadcrumb/Page.html.twig | 12 + .../shadcn/Breadcrumb/Separator.html.twig | 13 + .../components/shadcn/Button.html.twig | 43 + .../components/shadcn/Card.html.twig | 14 + .../components/shadcn/Card/Action.html.twig | 9 + .../components/shadcn/Card/Content.html.twig | 9 + .../shadcn/Card/Description.html.twig | 9 + .../components/shadcn/Card/Footer.html.twig | 9 + .../components/shadcn/Card/Header.html.twig | 9 + .../components/shadcn/Card/Title.html.twig | 9 + .../components/shadcn/Checkbox.html.twig | 15 + .../components/shadcn/Collapsible.html.twig | 16 + .../shadcn/Collapsible/Content.html.twig | 12 + .../shadcn/Collapsible/Trigger.html.twig | 10 + .../components/shadcn/Dialog.html.twig | 23 + .../components/shadcn/Dialog/Close.html.twig | 6 + .../shadcn/Dialog/Content.html.twig | 35 + .../shadcn/Dialog/Description.html.twig | 9 + .../components/shadcn/Dialog/Footer.html.twig | 9 + .../components/shadcn/Dialog/Header.html.twig | 9 + .../components/shadcn/Dialog/Title.html.twig | 9 + .../shadcn/Dialog/Trigger.html.twig | 8 + .../components/shadcn/DropdownMenu.html.twig | 27 + .../DropdownMenu/CheckboxItem.html.twig | 30 + .../shadcn/DropdownMenu/Content.html.twig | 48 + .../shadcn/DropdownMenu/Group.html.twig | 4 + .../shadcn/DropdownMenu/Item.html.twig | 43 + .../shadcn/DropdownMenu/Label.html.twig | 21 + .../shadcn/DropdownMenu/RadioGroup.html.twig | 13 + .../shadcn/DropdownMenu/RadioItem.html.twig | 33 + .../shadcn/DropdownMenu/Separator.html.twig | 8 + .../shadcn/DropdownMenu/Shortcut.html.twig | 9 + .../shadcn/DropdownMenu/Sub.html.twig | 19 + .../shadcn/DropdownMenu/SubContent.html.twig | 12 + .../shadcn/DropdownMenu/SubTrigger.html.twig | 30 + .../shadcn/DropdownMenu/Trigger.html.twig | 14 + .../components/shadcn/Empty.html.twig | 9 + .../components/shadcn/Empty/Content.html.twig | 9 + .../shadcn/Empty/Description.html.twig | 9 + .../components/shadcn/Empty/Header.html.twig | 9 + .../components/shadcn/Empty/Media.html.twig | 27 + .../components/shadcn/Empty/Title.html.twig | 9 + .../components/shadcn/Field.html.twig | 26 + .../components/shadcn/Field/Content.html.twig | 9 + .../shadcn/Field/Description.html.twig | 9 + .../components/shadcn/Field/Error.html.twig | 41 + .../components/shadcn/Field/Group.html.twig | 9 + .../components/shadcn/Field/Label.html.twig | 7 + .../components/shadcn/Field/Legend.html.twig | 14 + .../shadcn/Field/Separator.html.twig | 22 + .../components/shadcn/Field/Set.html.twig | 9 + .../components/shadcn/Field/Title.html.twig | 9 + .../components/shadcn/Input.html.twig | 6 + .../components/shadcn/InputGroup.html.twig | 11 + .../shadcn/InputGroup/Addon.html.twig | 27 + .../shadcn/InputGroup/Button.html.twig | 27 + .../shadcn/InputGroup/Input.html.twig | 4 + .../shadcn/InputGroup/Text.html.twig | 9 + .../shadcn/InputGroup/Textarea.html.twig | 7 + .../components/shadcn/Item.html.twig | 39 + .../components/shadcn/Item/Actions.html.twig | 9 + .../components/shadcn/Item/Content.html.twig | 9 + .../shadcn/Item/Description.html.twig | 9 + .../components/shadcn/Item/Footer.html.twig | 9 + .../components/shadcn/Item/Group.html.twig | 10 + .../components/shadcn/Item/Header.html.twig | 9 + .../components/shadcn/Item/Media.html.twig | 28 + .../shadcn/Item/Separator.html.twig | 11 + .../components/shadcn/Item/Title.html.twig | 9 + .../templates/components/shadcn/Kbd.html.twig | 9 + .../components/shadcn/KbdGroup.html.twig | 9 + .../components/shadcn/Label.html.twig | 9 + .../components/shadcn/NativeSelect.html.twig | 20 + .../shadcn/NativeSelect/OptGroup.html.twig | 4 + .../shadcn/NativeSelect/Option.html.twig | 4 + .../components/shadcn/Pagination.html.twig | 11 + .../shadcn/Pagination/Content.html.twig | 7 + .../shadcn/Pagination/Ellipsis.html.twig | 8 + .../shadcn/Pagination/Item.html.twig | 4 + .../shadcn/Pagination/Link.html.twig | 16 + .../shadcn/Pagination/Next.html.twig | 12 + .../shadcn/Pagination/Previous.html.twig | 12 + .../components/shadcn/RadioGroup.html.twig | 10 + .../shadcn/RadioGroup/Item.html.twig | 18 + .../components/shadcn/Separator.html.twig | 15 + .../components/shadcn/Sheet.html.twig | 26 + .../components/shadcn/Sheet/Close.html.twig | 6 + .../components/shadcn/Sheet/Content.html.twig | 47 + .../shadcn/Sheet/Description.html.twig | 9 + .../components/shadcn/Sheet/Footer.html.twig | 9 + .../components/shadcn/Sheet/Header.html.twig | 9 + .../components/shadcn/Sheet/Title.html.twig | 9 + .../components/shadcn/Sheet/Trigger.html.twig | 8 + .../components/shadcn/Sidebar.html.twig | 85 + .../shadcn/Sidebar/Content.html.twig | 9 + .../shadcn/Sidebar/Footer.html.twig | 9 + .../components/shadcn/Sidebar/Group.html.twig | 9 + .../shadcn/Sidebar/GroupAction.html.twig | 10 + .../shadcn/Sidebar/GroupContent.html.twig | 9 + .../shadcn/Sidebar/GroupLabel.html.twig | 9 + .../shadcn/Sidebar/Header.html.twig | 9 + .../components/shadcn/Sidebar/Input.html.twig | 7 + .../components/shadcn/Sidebar/Inset.html.twig | 9 + .../components/shadcn/Sidebar/Menu.html.twig | 9 + .../shadcn/Sidebar/MenuAction.html.twig | 17 + .../shadcn/Sidebar/MenuBadge.html.twig | 9 + .../shadcn/Sidebar/MenuButton.html.twig | 40 + .../shadcn/Sidebar/MenuItem.html.twig | 9 + .../shadcn/Sidebar/MenuSkeleton.html.twig | 16 + .../shadcn/Sidebar/MenuSub.html.twig | 9 + .../shadcn/Sidebar/MenuSubButton.html.twig | 30 + .../shadcn/Sidebar/MenuSubItem.html.twig | 9 + .../shadcn/Sidebar/Provider.html.twig | 36 + .../components/shadcn/Sidebar/Rail.html.twig | 18 + .../shadcn/Sidebar/Separator.html.twig | 6 + .../shadcn/Sidebar/Trigger.html.twig | 22 + .../components/shadcn/Skeleton.html.twig | 6 + .../components/shadcn/Spinner.html.twig | 12 + .../components/shadcn/Switch.html.twig | 23 + .../components/shadcn/Table.html.twig | 10 + .../components/shadcn/Table/Body.html.twig | 9 + .../components/shadcn/Table/Caption.html.twig | 9 + .../components/shadcn/Table/Cell.html.twig | 9 + .../components/shadcn/Table/Footer.html.twig | 9 + .../components/shadcn/Table/Head.html.twig | 9 + .../components/shadcn/Table/Header.html.twig | 9 + .../components/shadcn/Table/Row.html.twig | 9 + .../components/shadcn/Textarea.html.twig | 9 + .../components/shadcn/Tooltip.html.twig | 31 + .../shadcn/Tooltip/Content.html.twig | 43 + .../shadcn/Tooltip/Trigger.html.twig | 11 + .../security/common/content.html.twig | 7 + .../security/common/content/header.html.twig | 4 + .../templates/security/common/logo.html.twig | 3 + .../security/common/logo/image.html.twig | 9 + .../templates/security/common/page.html.twig | 5 + .../login/page/content/form.html.twig | 8 + .../login/page/content/form/error.html.twig | 8 + .../page/content/form/password.html.twig | 1 + .../page/content/form/remember_me.html.twig | 1 + .../login/page/content/form/submit.html.twig | 3 + .../page/content/form/username.html.twig | 3 + .../shared/components/badge.html.twig | 24 + .../components/confirmation_modal.html.twig | 44 + .../components/copy_to_clipboard.html.twig | 37 + .../shared/components/copyable.html.twig | 22 + .../shared/components/delete_modal.html.twig | 39 + .../templates/shared/components/dot.html.twig | 15 + .../shared/components/navbar/user.html.twig | 42 + .../components/sidebar_provider.html.twig | 37 + .../shared/crud/common/content.html.twig | 5 + .../crud/common/content/flashes.html.twig | 38 + .../crud/common/content/footer.html.twig | 21 + .../shared/crud/common/content/form.html.twig | 25 + .../common/content/form/sections.html.twig | 3 + .../content/form/sections/general.html.twig | 17 + .../form/sections/general/default.html.twig | 1 + .../common/content/form_error_alert.html.twig | 9 + .../crud/common/content/header.html.twig | 3 + .../content/header/title_block.html.twig | 3 + .../header/title_block/actions.html.twig | 10 + .../title_block/actions/cancel.html.twig | 12 + .../title_block/actions/create.html.twig | 5 + .../title_block/actions/update.html.twig | 9 + .../header/title_block/title.html.twig | 16 + .../shared/crud/common/navbar.html.twig | 6 + .../shared/crud/common/navbar/items.html.twig | 3 + .../navbar/items/theme_switcher.html.twig | 11 + .../crud/common/navbar/items/user.html.twig | 1 + .../shared/crud/common/navbar/menu.html.twig | 3 + .../common/navbar/sidebar_trigger.html.twig | 15 + .../shared/crud/common/sidebar.html.twig | 17 + .../shared/crud/common/sidebar/logo.html.twig | 11 + .../crud/common/sidebar/logo/image.html.twig | 22 + .../shared/crud/common/sidebar/menu.html.twig | 8 + .../crud/common/sidebar/menu/menu.html.twig | 142 ++ .../crud/common/sidebar/search.html.twig | 13 + .../crud/common/title/current.html.twig | 4 + .../shared/crud/common/title/parent.html.twig | 2 + .../crud/common/title/separator.html.twig | 1 + .../content/header/breadcrumbs.html.twig | 30 + .../header/title_block/actions.html.twig | 3 + .../header/title_block/title.html.twig | 17 + .../shared/crud/index/content/grid.html.twig | 3 + .../index/content/grid/data_table.html.twig | 48 + .../crud/index/content/grid/filters.html.twig | 68 + .../index/content/grid/no_results.html.twig | 7 + .../content/grid/no_results/action.html.twig | 8 + .../content/grid/no_results/image.html.twig | 9 + .../grid/no_results/subtitle.html.twig | 5 + .../content/grid/no_results/title.html.twig | 3 + .../content/header/breadcrumbs.html.twig | 15 + .../show/content/header/breadcrumbs.html.twig | 47 + .../header/title_block/title.html.twig | 17 + .../content/header/breadcrumbs.html.twig | 41 + .../header/title_block/actions.html.twig | 3 + .../header/title_block/title.html.twig | 17 + .../templates/shared/form_theme.html.twig | 338 ++++ .../grid/action/apply_transition.html.twig | 21 + .../shared/grid/action/create.html.twig | 9 + .../shared/grid/action/delete.html.twig | 16 + .../shared/grid/action/show.html.twig | 16 + .../shared/grid/action/update.html.twig | 16 + .../shared/grid/bulk_action/delete.html.twig | 24 + .../shared/grid/field/boolean.html.twig | 3 + .../shared/grid/filter/boolean.html.twig | 3 + .../shared/grid/filter/date.html.twig | 11 + .../shared/grid/filter/entity.html.twig | 3 + .../shared/grid/filter/enum.html.twig | 3 + .../shared/grid/filter/exists.html.twig | 3 + .../shared/grid/filter/select.html.twig | 3 + .../shared/grid/filter/string.html.twig | 13 + .../shared/helper/_checkbox.html.twig | 15 + .../shared/helper/accordion.html.twig | 18 + .../templates/shared/helper/avatar.html.twig | 15 + .../shared/helper/breadcrumbs.html.twig | 22 + .../templates/shared/helper/button.html.twig | 77 + .../shared/helper/dropdown.html.twig | 85 + .../shared/helper/field/boolean.html.twig | 13 + .../templates/shared/helper/header.html.twig | 21 + .../shared/helper/pagination.html.twig | 43 + .../templates/shared/helper/palette.html.twig | 38 + .../templates/shared/helper/sorting.html.twig | 29 + .../templates/shared/helper/table.html.twig | 108 ++ .../templates/shared/helper/ui.html.twig | 42 + .../shared/layout/javascripts.html.twig | 2 + .../shared/layout/metatags.html.twig | 1 + .../shared/layout/stylesheets.html.twig | 2 + .../templates/shared/layout/theme.html.twig | 10 + .../templates/shared/layout/title.html.twig | 1 + .../templates/shared/pagerfanta.html.twig | 56 + .../tests/Functional/.application/.env | 24 + .../tests/Functional/.application/.gitignore | 17 + .../Functional/.application/assets/admin.js | 4 + .../.application/assets/controllers.json | 4 + .../controllers/csrf_protection_controller.js | 85 + .../assets/icons/lucide/arrow-down.svg | 1 + .../assets/icons/lucide/arrow-right-left.svg | 1 + .../assets/icons/lucide/arrow-right.svg | 1 + .../assets/icons/lucide/arrow-up.svg | 1 + .../assets/icons/lucide/book-copy.svg | 1 + .../assets/icons/lucide/book-open.svg | 1 + .../assets/icons/lucide/check.svg | 1 + .../assets/icons/lucide/chevron-down.svg | 1 + .../assets/icons/lucide/chevron-left.svg | 1 + .../assets/icons/lucide/chevron-right.svg | 1 + .../assets/icons/lucide/chevrons-up-down.svg | 1 + .../assets/icons/lucide/circle-alert.svg | 1 + .../assets/icons/lucide/circle-check.svg | 1 + .../assets/icons/lucide/circle-x.svg | 1 + .../.application/assets/icons/lucide/copy.svg | 1 + .../assets/icons/lucide/ellipsis.svg | 1 + .../assets/icons/lucide/external-link.svg | 1 + .../.application/assets/icons/lucide/eye.svg | 1 + .../assets/icons/lucide/filter.svg | 1 + .../assets/icons/lucide/inbox.svg | 1 + .../.application/assets/icons/lucide/info.svg | 1 + .../assets/icons/lucide/layout-dashboard.svg | 1 + .../assets/icons/lucide/layout-grid.svg | 1 + .../assets/icons/lucide/library.svg | 1 + .../assets/icons/lucide/loader-2.svg | 1 + .../assets/icons/lucide/log-out.svg | 1 + .../.application/assets/icons/lucide/menu.svg | 1 + .../assets/icons/lucide/minus.svg | 1 + .../.application/assets/icons/lucide/moon.svg | 1 + .../assets/icons/lucide/more-horizontal.svg | 1 + .../assets/icons/lucide/panel-left.svg | 1 + .../assets/icons/lucide/pencil.svg | 1 + .../.application/assets/icons/lucide/plus.svg | 1 + .../assets/icons/lucide/rotate-ccw.svg | 1 + .../.application/assets/icons/lucide/save.svg | 1 + .../assets/icons/lucide/search.svg | 1 + .../assets/icons/lucide/server.svg | 1 + .../icons/lucide/sliders-horizontal.svg | 1 + .../.application/assets/icons/lucide/sun.svg | 1 + .../.application/assets/icons/lucide/tags.svg | 1 + .../assets/icons/lucide/trash-2.svg | 1 + .../assets/icons/lucide/triangle-alert.svg | 1 + .../.application/assets/icons/lucide/user.svg | 1 + .../assets/icons/lucide/warehouse.svg | 1 + .../.application/assets/icons/lucide/x.svg | 1 + .../.application/assets/styles/admin.css | 10 + .../tests/Functional/.application/bin/console | 21 + .../.application/config/bootstrap.php | 36 + .../.application/config/bundles.php | 34 + .../config/packages/asset_mapper.yaml | 6 + .../.application/config/packages/csrf.yaml | 7 + .../config/packages/doctrine.yaml | 15 + .../config/packages/framework.yaml | 11 + .../.application/config/packages/routing.yaml | 4 + .../config/packages/security.yaml | 29 + .../config/packages/sylius_resource.yaml | 15 + .../packages/sylius_shadcn_admin_ui.yaml | 8 + .../config/packages/sylius_twig_hooks.yaml | 51 + .../packages/symfonycasts_tailwind.yaml | 10 + .../config/packages/translation.yaml | 5 + .../.application/config/packages/twig.yaml | 6 + .../config/packages/twig_component.yaml | 5 + .../config/packages/ux_icons.yaml | 27 + .../config/packages/validator.yaml | 11 + .../.application/config/preload.php | 16 + .../.application/config/reference.php | 1679 +++++++++++++++++ .../.application/config/routes.yaml | 7 + .../.application/config/routes/security.yaml | 3 + .../config/routes/sylius_admin_ui.yaml | 3 + .../config/routes/sylius_resource.yaml | 3 + .../.application/config/services.yaml | 14 + .../Functional/.application/importmap.php | 21 + .../Functional/.application/public/index.php | 28 + .../src/Command/LoadBooksCommand.php | 86 + .../Command/UpdateShadcnRecipesCommand.php | 192 ++ .../.application/src/Entity/Book.php | 139 ++ .../.application/src/Enum/BookCategory.php | 36 + .../.application/src/Form/BookType.php | 53 + .../.application/src/Grid/BookGrid.php | 81 + .../Functional/.application/src/Kernel.php | 27 + .../src/Menu/AdminMenuBuilder.php | 61 + .../src/Twig/Components/DashboardStats.php | 64 + .../admin/book/show/details.html.twig | 48 + .../templates/admin/dashboard/stats.html.twig | 1 + .../templates/book/grid/category.html.twig | 1 + .../components/DashboardStats.html.twig | 88 + .../components/Fixture/ActionButton.html.twig | 1 + .../components/Fixture/ArrayClasses.html.twig | 1 + .../Fixture/ForwardingCard.html.twig | 1 + .../translations/messages.en.yaml | 27 + .../translations/messages.fr.yaml | 27 + .../tests/Functional/DemoApplicationTest.php | 127 ++ .../Functional/KitComponentsRenderingTest.php | 84 + .../tests/Functional/RendersTwigTrait.php | 33 + .../tests/Functional/UxCompatibilityTest.php | 121 ++ .../translations/messages.en.yaml | 8 + .../translations/messages.fr.yaml | 8 + 420 files changed, 12765 insertions(+) create mode 100644 docs/.gitbook/assets/shadcn-admin-ui-dashboard.png create mode 100644 docs/.gitbook/assets/shadcn-admin-ui-form.png create mode 100644 docs/.gitbook/assets/shadcn-admin-ui-list.png create mode 100644 docs/.gitbook/assets/shadcn-admin-ui-login.png create mode 100644 docs/shadcn-admin-ui/components.md create mode 100644 docs/shadcn-admin-ui/configuration.md create mode 100644 docs/shadcn-admin-ui/getting-started.md create mode 100644 docs/shadcn-admin-ui/grids.md create mode 100644 docs/shadcn-admin-ui/menu.md create mode 100644 docs/shadcn-admin-ui/migration.md create mode 100644 docs/shadcn-admin-ui/theming.md create mode 100644 src/ShadcnAdminUi/.gitattributes create mode 100644 src/ShadcnAdminUi/.gitignore create mode 100644 src/ShadcnAdminUi/LICENSE create mode 100644 src/ShadcnAdminUi/LICENSE_OF_TRADEMARK_AND_LOGO create mode 100644 src/ShadcnAdminUi/README.md create mode 100644 src/ShadcnAdminUi/assets/controllers/admin/copy_to_clipboard_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/admin/dismissible_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/admin/grid_selection_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/admin/menu_search_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/admin/sidebar_trigger_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/admin/tabs_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/admin/theme_switcher_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/alert_dialog_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/collapsible_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/dialog_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/dropdown_menu_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/dropdown_menu_sub_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/input_group_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/input_group_display_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/sidebar_controller.js create mode 100644 src/ShadcnAdminUi/assets/controllers/shadcn/tooltip_controller.js create mode 100644 src/ShadcnAdminUi/assets/styles/theme.css create mode 100644 src/ShadcnAdminUi/assets/styles/vendor/shadcn.css create mode 100644 src/ShadcnAdminUi/assets/styles/vendor/tw-animate.css create mode 100644 src/ShadcnAdminUi/composer.json create mode 100644 src/ShadcnAdminUi/config/app.php create mode 100644 src/ShadcnAdminUi/config/app/grid/templates.php create mode 100644 src/ShadcnAdminUi/config/app/twig_extra/twig_ux.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/common/create.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/common/index.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/common/show.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/common/update.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/dashboard/index.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/layout.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/layout/navbar.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/layout/sidebar.php create mode 100644 src/ShadcnAdminUi/config/app/twig_hooks/security/login.php create mode 100644 src/ShadcnAdminUi/config/services.php create mode 100644 src/ShadcnAdminUi/config/services/twig/component.php create mode 100644 src/ShadcnAdminUi/phpunit.xml.dist create mode 100644 src/ShadcnAdminUi/src/Symfony/SyliusShadcnAdminUiBundle.php create mode 100644 src/ShadcnAdminUi/src/Twig/Component/UserDropdownComponent.php create mode 100644 src/ShadcnAdminUi/src/Twig/Extension/UxTwoCompatibilityExtension.php create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Alert.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Alert/Action.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Alert/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Alert/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Action.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Cancel.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Media.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/AlertDialog/Trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Avatar.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Avatar/Badge.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Avatar/Fallback.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Avatar/Group.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Avatar/GroupCount.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Avatar/Image.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Badge.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Breadcrumb.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Breadcrumb/Ellipsis.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Breadcrumb/Item.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Breadcrumb/Link.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Breadcrumb/List.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Breadcrumb/Page.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Breadcrumb/Separator.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Button.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Card.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Card/Action.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Card/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Card/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Card/Footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Card/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Card/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Checkbox.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Collapsible.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Collapsible/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Collapsible/Trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog/Close.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog/Footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Dialog/Trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/CheckboxItem.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Group.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Item.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Label.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/RadioGroup.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/RadioItem.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Separator.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Shortcut.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Sub.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/SubContent.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/SubTrigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/DropdownMenu/Trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Empty.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Empty/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Empty/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Empty/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Empty/Media.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Empty/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Error.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Group.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Label.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Legend.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Separator.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Set.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Field/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Input.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/InputGroup.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/InputGroup/Addon.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/InputGroup/Button.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/InputGroup/Input.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/InputGroup/Text.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/InputGroup/Textarea.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Actions.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Group.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Media.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Separator.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Item/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Kbd.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/KbdGroup.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Label.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/NativeSelect.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/NativeSelect/OptGroup.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/NativeSelect/Option.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Pagination.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Pagination/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Pagination/Ellipsis.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Pagination/Item.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Pagination/Link.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Pagination/Next.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Pagination/Previous.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/RadioGroup.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/RadioGroup/Item.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Separator.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet/Close.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet/Description.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet/Footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet/Title.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sheet/Trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Group.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/GroupAction.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/GroupContent.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/GroupLabel.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Input.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Inset.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Menu.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuAction.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuBadge.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuButton.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuItem.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuSkeleton.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuSub.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuSubButton.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/MenuSubItem.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Provider.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Rail.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Separator.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Sidebar/Trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Skeleton.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Spinner.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Switch.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table/Body.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table/Caption.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table/Cell.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table/Footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table/Head.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table/Header.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Table/Row.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Textarea.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Tooltip.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Tooltip/Content.html.twig create mode 100644 src/ShadcnAdminUi/templates/components/shadcn/Tooltip/Trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/common/content.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/common/content/header.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/common/logo.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/common/logo/image.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/common/page.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/login/page/content/form.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/login/page/content/form/error.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/login/page/content/form/password.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/login/page/content/form/remember_me.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/login/page/content/form/submit.html.twig create mode 100644 src/ShadcnAdminUi/templates/security/login/page/content/form/username.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/badge.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/confirmation_modal.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/copy_to_clipboard.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/copyable.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/delete_modal.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/dot.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/navbar/user.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/components/sidebar_provider.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/flashes.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/footer.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/form.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/form/sections.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/form/sections/general.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/form/sections/general/default.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/form_error_alert.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/header.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/header/title_block.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/header/title_block/actions/cancel.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/header/title_block/actions/create.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/header/title_block/actions/update.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/content/header/title_block/title.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/navbar.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/navbar/items.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/navbar/items/theme_switcher.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/navbar/items/user.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/navbar/menu.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/navbar/sidebar_trigger.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/sidebar.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/sidebar/logo.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/sidebar/logo/image.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/sidebar/menu.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/sidebar/menu/menu.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/sidebar/search.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/title/current.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/title/parent.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/common/title/separator.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/actions.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/title.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid/filters.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/action.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/image.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/subtitle.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/title.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/show/content/header/title_block/title.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/actions.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/title.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/form_theme.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/action/apply_transition.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/action/create.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/action/delete.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/action/show.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/action/update.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/bulk_action/delete.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/field/boolean.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/filter/boolean.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/filter/date.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/filter/entity.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/filter/enum.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/filter/exists.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/filter/select.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/grid/filter/string.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/_checkbox.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/accordion.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/avatar.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/breadcrumbs.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/button.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/dropdown.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/field/boolean.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/header.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/pagination.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/palette.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/sorting.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/table.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/helper/ui.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/layout/javascripts.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/layout/metatags.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/layout/stylesheets.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/layout/theme.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/layout/title.html.twig create mode 100644 src/ShadcnAdminUi/templates/shared/pagerfanta.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/.env create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/.gitignore create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/admin.js create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/controllers.json create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/controllers/csrf_protection_controller.js create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-down.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right-left.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-up.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-copy.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-open.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/check.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-down.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-left.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-right.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevrons-up-down.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-alert.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-check.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-x.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/copy.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/ellipsis.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/external-link.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/eye.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/filter.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/inbox.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/info.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-dashboard.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-grid.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/library.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/loader-2.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/log-out.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/menu.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/minus.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/moon.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/more-horizontal.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/panel-left.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/pencil.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/plus.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/rotate-ccw.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/save.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/search.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/server.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sliders-horizontal.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sun.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/tags.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/trash-2.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/triangle-alert.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/user.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/warehouse.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/x.svg create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/assets/styles/admin.css create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/bin/console create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/bootstrap.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/bundles.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/asset_mapper.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/csrf.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/doctrine.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/framework.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/routing.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/security.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_resource.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_shadcn_admin_ui.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_twig_hooks.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/symfonycasts_tailwind.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/translation.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig_component.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/ux_icons.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/packages/validator.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/preload.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/reference.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/routes.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/routes/security.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_admin_ui.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/config/services.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/importmap.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/public/index.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Command/LoadBooksCommand.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Command/UpdateShadcnRecipesCommand.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Entity/Book.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Enum/BookCategory.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Form/BookType.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Grid/BookGrid.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Kernel.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Menu/AdminMenuBuilder.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/src/Twig/Components/DashboardStats.php create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/templates/admin/book/show/details.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/templates/admin/dashboard/stats.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/templates/book/grid/category.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/templates/components/DashboardStats.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ActionButton.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ArrayClasses.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ForwardingCard.html.twig create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/translations/messages.en.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/.application/translations/messages.fr.yaml create mode 100644 src/ShadcnAdminUi/tests/Functional/DemoApplicationTest.php create mode 100644 src/ShadcnAdminUi/tests/Functional/KitComponentsRenderingTest.php create mode 100644 src/ShadcnAdminUi/tests/Functional/RendersTwigTrait.php create mode 100644 src/ShadcnAdminUi/tests/Functional/UxCompatibilityTest.php create mode 100644 src/ShadcnAdminUi/translations/messages.en.yaml create mode 100644 src/ShadcnAdminUi/translations/messages.fr.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a645ae31..cf20a24f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -141,6 +141,22 @@ jobs: yarn install --frozen-lockfile --non-interactive yarn test + - name: "Restrict packages' versions (Shadcn Admin Ui)" + run: | + (cd src/ShadcnAdminUi/ && composer config --no-plugins allow-plugins.symfony/runtime true) + (cd src/ShadcnAdminUi/ && composer config extra.symfony.require "${{ matrix.symfony }}") + + - name: "Install dependencies (Shadcn Admin Ui)" + run: (cd src/ShadcnAdminUi/ && composer update --no-interaction --no-scripts) + + - name: "Build the assets (Shadcn Admin Ui)" + run: | + (cd src/ShadcnAdminUi/ && APP_ENV=test php tests/Functional/.application/bin/console importmap:install) + (cd src/ShadcnAdminUi/ && APP_ENV=test php tests/Functional/.application/bin/console tailwind:build) + + - name: Run PHPUnit (Shadcn Admin Ui) + run: (cd src/ShadcnAdminUi/ && vendor/bin/phpunit) + - name: "Restrict packages' versions (Twig Hooks)" run: | (cd src/TwigHooks/ && composer config --no-plugins allow-plugins.symfony/runtime true) diff --git a/.github/workflows/split_packages.yaml b/.github/workflows/split_packages.yaml index f68348cc..c93a1888 100644 --- a/.github/workflows/split_packages.yaml +++ b/.github/workflows/split_packages.yaml @@ -26,6 +26,9 @@ jobs: - directory: src/BootstrapAdminUi repository: BootstrapAdminUi + - + directory: src/ShadcnAdminUi + repository: ShadcnAdminUi - directory: src/TwigExtra repository: TwigExtra diff --git a/.gitignore b/.gitignore index 44cac802..f7c027cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ config/reference.php /src/**/.phpunit.result.cache /src/**/vendor +!/src/ShadcnAdminUi/assets/styles/vendor composer.lock ###> symfony/framework-bundle ### diff --git a/composer.json b/composer.json index dae6c206..f1a5eb32 100644 --- a/composer.json +++ b/composer.json @@ -74,6 +74,7 @@ "psr-4": { "Sylius\\AdminUi\\": "src/AdminUi/src/", "Sylius\\BootstrapAdminUi\\": "src/BootstrapAdminUi/src/", + "Sylius\\ShadcnAdminUi\\": "src/ShadcnAdminUi/src/", "Sylius\\TwigExtra\\": "src/TwigExtra/src/", "Sylius\\TwigHooks\\": "src/TwigHooks/src/", "Sylius\\UiTranslations\\": "src/UiTranslations/src/" @@ -85,10 +86,12 @@ "MainTests\\Sylius\\": "tests/", "TestApplication\\Sylius\\AdminUi\\": "src/AdminUi/tests/Functional/.application/src/", "TestApplication\\Sylius\\BootstrapAdminUi\\": "src/BootstrapAdminUi/tests/Functional/.application/src/", + "TestApplication\\Sylius\\ShadcnAdminUi\\": "src/ShadcnAdminUi/tests/Functional/.application/src/", "TestApplication\\Sylius\\TwigExtra\\": "src/TwigExtra/tests/Functional/.application/src/", "TestApplication\\Sylius\\TwigHooks\\": "src/TwigHooks/tests/Functional/.application/src/", "TestApplication\\Sylius\\UiTranslations\\": "src/UiTranslations/tests/Functional/.application/src/", "Tests\\Sylius\\AdminUi\\": "src/AdminUi/tests/", + "Tests\\Sylius\\ShadcnAdminUi\\": "src/ShadcnAdminUi/tests/", "Tests\\Sylius\\TwigExtra\\": "src/TwigExtra/tests/", "Tests\\Sylius\\TwigHooks\\": "src/TwigHooks/tests/", "Tests\\Sylius\\UiTranslations\\": "src/UiTranslations/tests/" @@ -97,6 +100,7 @@ "replace": { "sylius/admin-ui": "self.version", "sylius/bootstrap-admin-ui": "self.version", + "sylius/shadcn-admin-ui": "self.version", "sylius/twig-extra": "self.version", "sylius/twig-hooks": "self.version", "sylius/ui-translations": "self.version" diff --git a/docs/.gitbook/assets/shadcn-admin-ui-dashboard.png b/docs/.gitbook/assets/shadcn-admin-ui-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..142e469512d96091505c8c0a991500ad143f62e3 GIT binary patch literal 76350 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/shadcn-admin-ui-form.png b/docs/.gitbook/assets/shadcn-admin-ui-form.png new file mode 100644 index 0000000000000000000000000000000000000000..a42cb734352fe18fbc75b7fa1487eab6c90c70ec GIT binary patch literal 46808 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/shadcn-admin-ui-list.png b/docs/.gitbook/assets/shadcn-admin-ui-list.png new file mode 100644 index 0000000000000000000000000000000000000000..1c6af1de5bcecd87521e02c9ac2920b8b8a6b67d GIT binary patch literal 117286 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

=cAI2%&N>P(5M!^J~ld!vlAR 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/shadcn-admin-ui-login.png b/docs/.gitbook/assets/shadcn-admin-ui-login.png new file mode 100644 index 0000000000000000000000000000000000000000..96541d42c13bf31c96d328375fe8204714fdbd97 GIT binary patch literal 22193 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 + + Stock + + + 42 parcels + + +``` + +| Category | Components | +|---------------|----------------------------------------------------------------------------------------------| +| Layout | `Card`, `Separator`, `Sidebar`, `Item`, `Empty`, `Skeleton` | +| Forms | `Field`, `Input`, `InputGroup`, `Label`, `NativeSelect`, `Checkbox`, `RadioGroup`, `Switch`, `Textarea` | +| Actions | `Button`, `DropdownMenu`, `Kbd`, `KbdGroup` | +| Overlays | `Dialog`, `AlertDialog`, `Sheet`, `Tooltip` | +| Feedback | `Alert`, `Badge`, `Spinner` | +| Navigation | `Breadcrumb`, `Pagination`, `Collapsible` | +| Data | `Avatar`, `Table` | + +{% hint style="info" %} +The [shadcn kit documentation](https://ux.symfony.com/toolkit/kits/shadcn) describes the props and sub-components +of each recipe. +{% endhint %} + +## Sylius components + +Same components as BootstrapAdminUi, under the `sylius_shadcn_admin_ui:` prefix. + +| Component | Purpose | Main props | +|--------------------------------------|-------------------------------------------------|-------------------------------------------------------------------| +| `sylius_shadcn_admin_ui:delete_modal` | Delete button + confirmation dialog + CSRF form | `id`, `path`, `label`, `icon`, `icon_only`, `disabled`, `form_attr` | +| `sylius_shadcn_admin_ui:confirmation_modal` | Generic confirmation dialog | `modal_id`, `title`, `description`, `size`; blocks `trigger`, `footer` | +| `sylius_shadcn_admin_ui:copyable` | Value followed by a copy button | `value`, `copy_label`, `copied_label`, `error_label` | +| `sylius_shadcn_admin_ui:copy_to_clipboard` | Copy button alone | `value`, `button_class`, `disabled` | +| `sylius_shadcn_admin_ui:badge` | Colored badge | `color`, `light`, `href`, `label` | +| `sylius_shadcn_admin_ui:dot` | Status dot | `color`, `pulse` | + +```twig +Paid + + + + +``` + +`color` accepts every name of the [tone palette](theming.md#the-tone-macro). The `pulse` halo is disabled when the +user prefers reduced motion. + +## Macros + +Same macro APIs as `@SyliusBootstrapAdminUi`: only the namespace changes. + +| Template (`@SyliusShadcnAdminUi/shared/helper/…`) | Macros | +|--------------------------------------------------|---------------------------------------------------------------------| +| `button.html.twig` | `default`, `primary`, `delete`, `create`, `update`, `cancel` | +| `breadcrumbs.html.twig` | `breadcrumbs(items)` | +| `header.html.twig` | `default(level, header, icon, subheader)`, `h1(header, icon, subheader)` | +| `dropdown.html.twig` | `default(params)`, `list(params, items)`, `item(attributes, content, variant)` | +| `table.html.twig` | `headers`, `row` | +| `pagination.html.twig` | `simple(paginator, options)`, `results_count(paginator)` | +| `accordion.html.twig` | `accordion(items, open, auto_close, flush)` | +| `avatar.html.twig` | `default(img, class, initials)`, `small(img, class, initials)` | +| `palette.html.twig` | [`tone(color, kind)`](theming.md#the-tone-macro) | + +The button macros accept an extra `variant` param: `default`, `secondary`, `outline`, `ghost`, `destructive`, `link`. + +```twig +{% import '@SyliusShadcnAdminUi/shared/helper/button.html.twig' as button %} + +{{ button.primary({text: 'Export', icon: 'lucide:download', url: path('app_export')}) }} +{{ button.default({text: 'Archive', variant: 'ghost', size: 'small'}) }} +``` + +## Tabs + +Accessible tabs with the `admin--tabs` controller (`tab` and `panel` targets): + +- keyboard navigation: arrows, Home, End (roving tabindex); +- the selected tab is kept in the URL hash; +- tabs holding a validation error are flagged and opened on submit. + +```twig +

+
+ + +
+
…
+
…
+
+``` + +## Form theme + +The admin forms use the shadcn form theme. Use it anywhere: + +```twig +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} +``` + +Render a checkbox as a switch with `label_attr: {class: 'checkbox-switch'}` or `attr: {role: 'switch'}`: + +```php +$builder->add('enabled', CheckboxType::class, ['attr' => ['role' => 'switch']]); +``` + +## Stimulus controllers + +Registered automatically by the bundle. + +| Identifier | Role | +|---------------------------------------------|------------------------------------------------------------------| +| `admin--tabs` | Accessible tabs ([see above](#tabs)) | +| `admin--grid-selection` | Row selection, "select all", bulk action forms | +| `admin--copy-to-clipboard` | Copy buttons | +| `admin--dismissible` | Closable flash messages and alerts | +| `admin--menu-search` | Sidebar menu search | +| `admin--sidebar-trigger` | Navbar button that toggles the sidebar | +| `admin--theme-switcher` | Light / dark switch | +| `shadcn--sidebar`, `shadcn--dialog`, `shadcn--alert-dialog`, `shadcn--dropdown-menu`, `shadcn--dropdown-menu-sub`, `shadcn--collapsible`, `shadcn--tooltip`, `shadcn--input-group`, `shadcn--input-group-display` | Behaviour of the shadcn recipes | + +## Symfony UX 2 + +The shadcn kit relies on Symfony UX 3.5+ features. Under Symfony UX 2, the package registers a compatibility extension that provides `provide()`, `inject()` and the conflict resolution of `tailwind_classes`. + +{% hint style="warning" %} +Under Symfony UX 2, `tailwind_classes` assumes the classes of the caller apply to the element using the filter. Use it on the root element of your components, through `attributes.defaults()`: on an inner element, default classes conflicting with the caller classes are dropped. +{% endhint %} + +## Updating the shadcn recipes + +The components of `templates/components/shadcn/` and the controllers of `assets/controllers/shadcn/` are recipes of +the [shadcn kit of Symfony UX Toolkit](https://ux.symfony.com/toolkit/kits/shadcn), renamed for the bundle. +`app:shadcn:update`, a command of the test application, installs them again from `symfony/ux-toolkit` and applies the renaming. + +{% hint style="warning" %} +`symfony/ux-toolkit` requires PHP 8.4 and is not a dependency of the package: run this procedure on PHP 8.4+, with `symfony/ux-toolkit` installed and `Symfony\UX\Toolkit\UXToolkitBundle` enabled in the `dev` environment of the test application. Do not commit these two changes. +{% endhint %} + +| Recipe | Bundle | +|------------------------------------------|---------------------------------------------------| +| `templates/components/Button.html.twig` | `templates/components/shadcn/Button.html.twig` | +| `assets/controllers/dialog_controller.js`| `assets/controllers/shadcn/dialog_controller.js` | +| ``, `component('Button')` | ``, `component('shadcn:Button')` | +| `data-controller="dialog"`, `click->dialog#open`, `data-dialog-target`, `data-dialog-open-value` | `shadcn--dialog` everywhere | + +### 1. Update the kit + +```bash +composer update symfony/ux-toolkit +``` + +### 2. Install the recipes + +```bash +APP_ENV=dev php tests/Functional/.application/bin/console app:shadcn:update # every bundled recipe +APP_ENV=dev php tests/Functional/.application/bin/console app:shadcn:update button dialog # some of them +``` + +{% hint style="info" %} +Without argument, the bundled recipes are deduced from the components of `templates/components/shadcn/` +(`AlertDialog.html.twig` → `alert-dialog`). Recipe dependencies (e.g. `button` for `dialog`) are installed too. +To add a new recipe, pass its name. +{% endhint %} + +### 3. Review the diff + +The files are **overwritten**: `git diff` shows both the changes of the kit and the local changes of the bundle that +were lost. Keep the former, restore the latter (`git checkout -p`). + +Local changes to restore after an update (as of UX Toolkit 3.6): + +| File | Local change | +|------------------------------------------------------------|------------------------------------------------------------------------------| +| `DropdownMenu/{Item,CheckboxItem,RadioItem,SubTrigger}` | `cursor-pointer` instead of `cursor-default` | +| `DropdownMenu/Content` | `left` / `right` sides combined with `align` (`start`, `center`, `end`) | +| `dropdown_menu_controller.js` | A pointer click focuses the menu, not its first item (no highlighted item) | +| `Sidebar` | Nested sidebar (`sidebar.nested`: `
` instead of `
{% endif %} +
+
+ {{ block('label') }} +
+
    + {% set group_open = true %} + {% else %} + {% if not group_open %} +
    +
      + {% set group_open = true %} + {% endif %} + {{ block('item') }} + {% endif %} + {% endfor %} + {% if group_open %}
    {% endif %} +{% endblock %} + +{% block item %} + {% if item.displayed %} + {%- set button_class = 'peer/menu-button flex h-8 w-full items-center gap-2 overflow-hidden rounded-md p-2 text-start text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:[&>:not(:first-child)]:hidden [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0' -%} + {%- set is_current = matcher.isCurrent(item) -%} + {%- set is_open = is_current or matcher.isAncestor(item, options.matchingDepth) or item.getExtra('always_open', false) -%} + {%- set target = item.linkAttributes and item.linkAttributes['target'] is defined ? item.linkAttributes['target'] : '_self' -%} + {%- set has_children = item.hasChildren and item.displayChildren and options.depth is not same as(0) -%} + +
  • + {% if has_children %} +
    + +
    +
      + {% set parent = item %} + {# Children after a header are indented under it as a tree branch. #} + {% set in_sub_section = false %} + {% for item in parent.children %} + {% if item.displayed and item.getExtra('header', false) %}{% set in_sub_section = true %}{% endif %} + {{ block('child_item') }} + {% endfor %} + {% set item = parent %} +
    +
    +
    + {% else %} + + {{ block('icon') }} + {{ block('label') }} + {% if target == '_blank' %}{{ ux_icon('lucide:external-link', {class: 'ms-auto size-3.5! opacity-60', 'aria-hidden': 'true'}) }}{% endif %} + + {% endif %} +
  • + {% endif %} +{% endblock %} + +{% block child_item %} + {% if item.displayed and item.getExtra('header', false) %} + {# Sub-section title drawn as a node on the sub menu vertical line (border-s of sidebar-menu-sub, px-2.5 away). #} +
  • + + {{ block('label') }} +
  • + {% elseif item.displayed %} + {%- set is_current = matcher.isCurrent(item) or matcher.isAncestor(item, options.matchingDepth) -%} + {%- set target = item.linkAttributes and item.linkAttributes['target'] is defined ? item.linkAttributes['target'] : '_self' -%} + {# The branch line starts under the header label (px-2) and covers the gap-1 above to stay continuous. #} +
  • + + {{ block('label') }} + +
  • + {% endif %} +{% endblock %} + +{% block icon %} + {%- set icon_name = item.labelAttribute('icon') -%} + {%- if icon_name -%} + {{ ux_icon(icon_name, {'aria-hidden': 'true'}) }} + {%- elseif item.level == 1 -%} + {# Keeps something visible when the sidebar is collapsed to icons. #} + + {%- endif -%} +{% endblock %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/common/sidebar/search.html.twig b/src/ShadcnAdminUi/templates/shared/crud/common/sidebar/search.html.twig new file mode 100644 index 00000000..2f488f08 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/common/sidebar/search.html.twig @@ -0,0 +1,13 @@ +
    + + {{ ux_icon('lucide:search', {class: 'pointer-events-none absolute start-4 top-1/2 mt-1 size-4 -translate-y-1/2 text-muted-foreground', 'aria-hidden': 'true'}) }} + +
    diff --git a/src/ShadcnAdminUi/templates/shared/crud/common/title/current.html.twig b/src/ShadcnAdminUi/templates/shared/crud/common/title/current.html.twig new file mode 100644 index 00000000..e86d52ce --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/common/title/current.html.twig @@ -0,0 +1,4 @@ +{% set metadata = hookable_metadata.context.metadata %} +{% set header = hookable_metadata.configuration.header|default(metadata.applicationName ~ '.ui.' ~ metadata.pluralName) %} + +{{ header|trans }} diff --git a/src/ShadcnAdminUi/templates/shared/crud/common/title/parent.html.twig b/src/ShadcnAdminUi/templates/shared/crud/common/title/parent.html.twig new file mode 100644 index 00000000..2139f921 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/common/title/parent.html.twig @@ -0,0 +1,2 @@ +{% set parent = hookable_metadata.context.parent %} +{{ parent }} diff --git a/src/ShadcnAdminUi/templates/shared/crud/common/title/separator.html.twig b/src/ShadcnAdminUi/templates/shared/crud/common/title/separator.html.twig new file mode 100644 index 00000000..948cf947 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/common/title/separator.html.twig @@ -0,0 +1 @@ +| diff --git a/src/ShadcnAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig b/src/ShadcnAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig new file mode 100644 index 00000000..6da95d61 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig @@ -0,0 +1,30 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/breadcrumbs.html.twig' import breadcrumbs %} + +{% if hookable_metadata.context.metadata is defined %} + {% set metadata = hookable_metadata.context.metadata %} + {% set resource_name = hookable_metadata.configuration.resource_name|default(metadata.applicationName ~ '.ui.' ~ metadata.pluralName) %} +{% else %} + {% set resource_name = hookable_metadata.configuration.resource_name|default(null) %} +{% endif %} + +{% set configuration = hookable_metadata.context.configuration|default(null) %} + +{% if configuration is not null %} + {% set index_url = path( + configuration.vars.index.route.name|default(configuration.getRouteName('index')), + configuration.vars.index.route.parameters|default(configuration.vars.route.parameters|default({})), + ) %} +{% endif %} + +{% if index_url is defined and resource_name is not null %} + {{ breadcrumbs([ + {name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false}, + {name: resource_name, url: index_url, active: false}, + {name: 'sylius.ui.new'|trans, active: true}, + ]) }} +{% else %} + {{ breadcrumbs([ + {name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false}, + {name: 'sylius.ui.new'|trans, active: true}, + ]) }} +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/actions.html.twig b/src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/actions.html.twig new file mode 100644 index 00000000..b9fe554c --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/actions.html.twig @@ -0,0 +1,3 @@ +
    + {% hook 'actions' %} +
    diff --git a/src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/title.html.twig b/src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/title.html.twig new file mode 100644 index 00000000..45b4e59f --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/create/content/header/title_block/title.html.twig @@ -0,0 +1,17 @@ +{% import '@SyliusShadcnAdminUi/shared/helper/header.html.twig' as header %} + +{% if hookable_metadata.context.metadata is defined and hookable_metadata.context.configuration is defined %} + {% set metadata = hookable_metadata.context.metadata %} + {% set singular_name = hookable_metadata.configuration.resource_name|default(metadata.applicationName ~ '.ui.' ~ metadata.name) %} +{% else %} + {% set singular_name = hookable_metadata.configuration.resource_name|default(null) %} +{% endif %} +{% set header = hookable_metadata.configuration.title|default(hookable_metadata.configuration.header|default(null)) %} + +{% set icon = hookable_metadata.configuration.icon|default(null) %} +{% set subheader = hookable_metadata.configuration.subheader|default(null) %} +{% set test_attribute = hookable_metadata.configuration.sylius_test_html_attribute is defined ? sylius_test_html_attribute(hookable_metadata.configuration.sylius_test_html_attribute) : null %} + +
    + {{ header.h1(header is not null ? header|trans : 'sylius.ui.new'|trans ~ ' ' ~ singular_name|trans, icon, subheader|trans) }} +
    diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid.html.twig new file mode 100644 index 00000000..991660e7 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid.html.twig @@ -0,0 +1,3 @@ +
    + {% hook 'grid' %} +
    diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig new file mode 100644 index 00000000..e5673e26 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig @@ -0,0 +1,48 @@ +{% import '@SyliusShadcnAdminUi/shared/helper/table.html.twig' as table %} +{% import '@SyliusShadcnAdminUi/shared/helper/pagination.html.twig' as pagination %} + +{% set resources = resources|default(hookable_metadata.context.resources|default(null)) %} +{% set data = resources.data|default([]) %} +{% set definition = resources.definition|default(null) %} +{% set has_bulk_actions = definition.actionGroups.bulk is defined and definition.getEnabledActions('bulk')|length > 0 %} + +{% if data|length > 0 %} +
    +
    + {% if has_bulk_actions %} +
    + {% for action in definition.getEnabledActions('bulk')|sylius_sort_by('position') %} + {{ sylius_grid_render_bulk_action(resources, action, null) }} + {% endfor %} +
    + {% endif %} +
    + {{ pagination.number_of_results_selector(data, definition.limits) }} +
    +
    + +
    + + + + {{ table.headers(resources, resources.definition, app.request.attributes) }} + + + + {% for row in resources.data %} + {{ table.row(resources, resources.definition, row) }} + {% endfor %} + +
    +
    + +
    +
    + {{ pagination.results_count(data) }} +
    +
    + {{ pagination.simple(data) }} +
    +
    +
    +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/filters.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/filters.html.twig new file mode 100644 index 00000000..96d50fc5 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/filters.html.twig @@ -0,0 +1,68 @@ +{# + # Collapsible filters panel. Hookable configuration: + # open: true / false, open on load when criteria are set by default + # indicator: false hides the dot of the trigger shown when criteria are set + # Inner hooks: "filters#trigger" (inside the trigger, after its title) and "filters#summary" (between the trigger + # and the form, visible when the panel is collapsed). + #} +{% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + +{% set resources = resources|default(hookable_metadata.context.resources|default(null)) %} +{% set path = path(app.request.attributes.get('_route'), app.request.attributes.all('_route_params')) %} +{% set criteria = app.request.query.all('criteria') %} +{% set configuration = hookable_metadata.configuration|default({}) %} +{% set open = configuration.open ?? (criteria is not empty) %} +{% set enabled_filters = resources.definition.enabledFilters|default([])|filter(filter => filter.enabled) %} + +{% if enabled_filters is not empty %} +
    + + + {% hook 'filters#summary' %} + +
    +
    + {% if app.request.query.has('limit') %} + + {% endif %} +
    + {% for filter in enabled_filters|sylius_sort_by('position') %} +
    + {{ sylius_grid_render_filter(resources, filter) }} +
    + {% endfor %} +
    +
    + + + {{ ux_icon('lucide:rotate-ccw', {'data-icon': 'inline-start', 'aria-hidden': 'true'}) }} + {{ 'sylius.ui.reset'|trans }} + +
    +
    +
    +
    +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig new file mode 100644 index 00000000..5d829bc5 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig @@ -0,0 +1,7 @@ +{% set resources = resources|default(hookable_metadata.context.resources|default(null)) %} + +{% if resources.data is defined and resources.data|length == 0 %} +
    + {% hook 'no_results' %} +
    +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/action.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/action.html.twig new file mode 100644 index 00000000..0bf197c2 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/action.html.twig @@ -0,0 +1,8 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + +{% if _context.route is defined and (_context.hidden is not defined or _context.hidden == false) %} + + {{ ux_icon(_context.icon|default('lucide:plus'), {'data-icon': 'inline-start', 'aria-hidden': 'true'}) }} + {{ _context.translation_key|default('sylius.ui.add_new_entry')|trans }} + +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/image.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/image.html.twig new file mode 100644 index 00000000..16a7c1bb --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/image.html.twig @@ -0,0 +1,9 @@ +{% if _context.hidden is not defined or _context.hidden == false %} +
    + {% if _context.image is defined %} + + {% else %} + {{ ux_icon(_context.icon|default('lucide:inbox'), {class: 'size-5', 'aria-hidden': 'true'}) }} + {% endif %} +
    +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/subtitle.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/subtitle.html.twig new file mode 100644 index 00000000..479b5e70 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/subtitle.html.twig @@ -0,0 +1,5 @@ +{% if _context.hidden is not defined or _context.hidden == false %} +

    + {{ _context.translation_key|default('sylius.ui.no_results_adjust_your_search')|trans }} +

    +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/title.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/title.html.twig new file mode 100644 index 00000000..c245cbb4 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/grid/no_results/title.html.twig @@ -0,0 +1,3 @@ +{% if _context.hidden is not defined or _context.hidden == false %} +

    {{ _context.translation_key|default('sylius.ui.no_results')|trans }}

    +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig b/src/ShadcnAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig new file mode 100644 index 00000000..c97a908a --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig @@ -0,0 +1,15 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/breadcrumbs.html.twig' import breadcrumbs %} + +{% set title_from_context = hookable_metadata.context.title|default(null) %} +{% set resource_metadata = hookable_metadata.context.metadata|default(null) %} + +{% if title_from_context is not null or resource_metadata is not null %} + {% set title = title_from_context|default(resource_metadata.applicationName ~ '.ui.' ~ resource_metadata.pluralName) %} +{% else %} + {% set title = hookable_metadata.configuration.title|default(null) %} +{% endif %} + +{{ breadcrumbs([ + {name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false}, + {name: title|default('sylius.ui.index'|trans), active: true}, +]) }} diff --git a/src/ShadcnAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig b/src/ShadcnAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig new file mode 100644 index 00000000..3b000b4c --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig @@ -0,0 +1,47 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/breadcrumbs.html.twig' import breadcrumbs %} + +{% set configuration = hookable_metadata.context.configuration|default(null) %} +{% set resource = hookable_metadata.context.resource|default(null) %} +{% set metadata = hookable_metadata.context.metadata|default(null) %} + +{% if configuration and metadata %} + {% set resource_index = metadata.applicationName ~ '.ui.' ~ metadata.pluralName %} + {% set index_url = path( + configuration.vars.index.route.name|default(configuration.getRouteName('index')), + configuration.vars.index.route.parameters|default(configuration.vars.route.parameters|default({})), + ) %} +{% endif %} + +{% set title = hookable_metadata.configuration.title|default(null) %} +{% set rendered_field_prefix = hookable_metadata.configuration.rendered_field_prefix|default(null) %} +{% set field_name = hookable_metadata.configuration.rendered_field|default('') %} +{% set rendered_field = rendered_field_prefix ~ (attribute(resource, field_name) is defined ? attribute(resource, field_name) : null) %} + +{% set action = hookable_metadata.configuration.action|default(false) + ? {name: hookable_metadata.configuration.action, active: false} + : {} +%} + +{% set resource_show_name = title|default(rendered_field|default(resource.translations[app.locale].name|default(resource.code|default(resource.id|default(null))))) %} + +{% if action is defined and action.name is defined %} + {% set show_url = path(configuration.getRouteName('show'), {id: resource.id}) %} + {% set resource_show = {name: resource_show_name, url: show_url} %} +{% else %} + {% set resource_show = {name: resource_show_name|default('sylius.ui.show'|trans), active: true} %} +{% endif %} + +{% if resource_index is defined and index_url is defined %} + {{ breadcrumbs([ + {name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false}, + {name: resource_index, url: index_url, active: false}, + resource_show, + action, + ]) }} +{% else %} + {{ breadcrumbs([ + {name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false}, + resource_show, + action, + ]) }} +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/show/content/header/title_block/title.html.twig b/src/ShadcnAdminUi/templates/shared/crud/show/content/header/title_block/title.html.twig new file mode 100644 index 00000000..699f7ec0 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/show/content/header/title_block/title.html.twig @@ -0,0 +1,17 @@ +{% import '@SyliusShadcnAdminUi/shared/helper/header.html.twig' as header %} + +{% if hookable_metadata.context.metadata is defined and hookable_metadata.context.configuration is defined %} + {% set metadata = hookable_metadata.context.metadata %} + {% set singular_name = hookable_metadata.configuration.resource_name|default(metadata.applicationName ~ '.ui.' ~ metadata.name) %} +{% else %} + {% set singular_name = hookable_metadata.configuration.resource_name|default(null) %} +{% endif %} +{% set header = hookable_metadata.configuration.title|default(hookable_metadata.configuration.header|default(null)) %} + +{% set icon = hookable_metadata.configuration.icon|default(null) %} +{% set subheader = hookable_metadata.configuration.subheader|default(null) %} +{% set test_attribute = hookable_metadata.configuration.sylius_test_html_attribute is defined ? sylius_test_html_attribute(hookable_metadata.configuration.sylius_test_html_attribute) : null %} + +
    + {{ header.h1(header is not null ? header|trans : 'sylius.ui.show'|trans ~ ' ' ~ singular_name|trans, icon, subheader|trans) }} +
    diff --git a/src/ShadcnAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig b/src/ShadcnAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig new file mode 100644 index 00000000..684e7d9c --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig @@ -0,0 +1,41 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/breadcrumbs.html.twig' import breadcrumbs %} + +{% set configuration = hookable_metadata.context.configuration|default(null) %} +{% set resource = hookable_metadata.context.resource|default(null) %} +{% set metadata = hookable_metadata.context.metadata|default(null) %} + +{% if metadata is not null and configuration is not null %} + {% set resource_index = metadata.applicationName ~ '.ui.' ~ metadata.pluralName %} + {% set index_url = path( +configuration.vars.index.route.name|default(configuration.getRouteName('index')), +configuration.vars.index.route.parameters|default(configuration.vars.route.parameters|default({})), + ) %} +{% endif %} + +{% set rendered_field_prefix = hookable_metadata.configuration.rendered_field_prefix|default(null) %} +{% set field_name = hookable_metadata.configuration.rendered_field|default('') %} +{% set rendered_field = rendered_field_prefix ~ (attribute(resource, field_name) is defined ? attribute(resource, field_name) : null) %} + +{% set resource_edit_name = rendered_field|default(resource.translations[app.locale].name|default(resource.code|default(resource.id|default(null)))) %} +{% set show_route_name = configuration.vars.index.route.name|default(configuration.getRouteName('show')|default(null)) %} + +{% if show_route_name is not null and sylius_route_exists(show_route_name) %} + {% set show_url = path(show_route_name, {id: resource.id}) %} + {% set resource_edit = {name: resource_edit_name, url: show_url} %} +{% else %} + {% set resource_edit = {name: resource_edit_name, active: true} %} +{% endif %} + +{% if resource_index is defined %} + {{ breadcrumbs([ + {name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false}, + {name: resource_index, url: index_url, active: false}, + resource_edit, + {name: 'sylius.ui.edit'|trans, active: false}, + ]) }} +{% else %} + {{ breadcrumbs([ + {name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false}, + {name: 'sylius.ui.edit'|trans, active: false}, + ]) }} +{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/actions.html.twig b/src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/actions.html.twig new file mode 100644 index 00000000..b9fe554c --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/actions.html.twig @@ -0,0 +1,3 @@ +
    + {% hook 'actions' %} +
    diff --git a/src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/title.html.twig b/src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/title.html.twig new file mode 100644 index 00000000..ee56104e --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/crud/update/content/header/title_block/title.html.twig @@ -0,0 +1,17 @@ +{% import '@SyliusShadcnAdminUi/shared/helper/header.html.twig' as header %} + +{% if hookable_metadata.context.metadata is defined and hookable_metadata.context.configuration is defined %} + {% set metadata = hookable_metadata.context.metadata %} + {% set singular_name = hookable_metadata.configuration.resource_name|default(metadata.applicationName ~ '.ui.' ~ metadata.name) %} +{% else %} + {% set singular_name = hookable_metadata.configuration.resource_name|default(null) %} +{% endif %} +{% set header = hookable_metadata.configuration.title|default(hookable_metadata.configuration.header|default(null)) %} + +{% set icon = hookable_metadata.configuration.icon|default(null) %} +{% set subheader = hookable_metadata.configuration.subheader|default(null) %} +{% set test_attribute = hookable_metadata.configuration.sylius_test_html_attribute is defined ? sylius_test_html_attribute(hookable_metadata.configuration.sylius_test_html_attribute) : null %} + +
    + {{ header.h1(header is not null ? header|trans : 'sylius.ui.edit'|trans ~ ' ' ~ singular_name|trans, icon, subheader|trans) }} +
    diff --git a/src/ShadcnAdminUi/templates/shared/form_theme.html.twig b/src/ShadcnAdminUi/templates/shared/form_theme.html.twig new file mode 100644 index 00000000..04f35ba6 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/form_theme.html.twig @@ -0,0 +1,338 @@ +{# + # Symfony form theme with the shadcn/ui look (Input, Textarea, NativeSelect, Checkbox, Switch, + # Label, Field). Classes are copied from the UX Toolkit shadcn recipes: keep them in sync when + # you re-install a recipe. + # + # Use it in your own templates with: + # {% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + # + # Switch: add 'checkbox-switch' to the label_attr class (Bootstrap convention) or role="switch" to attr. + #} +{% extends 'form_div_layout.html.twig' %} + +{# ----- Shared class strings ----------------------------------------------------------------- #} + +{%- block shadcn_input_class -%} + h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:[color-scheme:dark] +{%- endblock shadcn_input_class -%} + +{%- block shadcn_textarea_class -%} + flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 +{%- endblock shadcn_textarea_class -%} + +{%- block shadcn_select_class -%} + h-8 w-full min-w-0 appearance-none rounded-lg border border-input bg-transparent py-1 ps-2.5 pe-8 text-sm transition-colors outline-none select-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:[color-scheme:dark] [&_option]:bg-popover [&_option]:text-popover-foreground +{%- endblock shadcn_select_class -%} + +{%- block shadcn_checkbox_class -%} + peer col-start-1 row-start-1 size-4 appearance-none rounded-[4px] border border-input bg-transparent transition-colors outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 checked:border-primary checked:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:bg-input/30 dark:checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 forced-colors:appearance-auto +{%- endblock shadcn_checkbox_class -%} + +{%- block shadcn_radio_class -%} + size-4 shrink-0 appearance-none rounded-full border border-input bg-transparent transition-all outline-none checked:border-[5px] checked:border-primary focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:bg-input/30 dark:checked:border-primary +{%- endblock shadcn_radio_class -%} + +{# ----- Form ----------------------------------------------------------------------------------- #} + +{%- block form_start -%} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ['GET', 'POST'] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = 'POST' %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} +{%- endblock form_start -%} + +{%- block form_widget_compound -%} + {%- set attr = attr|merge({class: ('flex flex-col gap-6 ' ~ attr.class|default(''))|trim}) -%} +
    + {%- if form is not rootform -%} + {{- form_errors(form) -}} + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
    +{%- endblock form_widget_compound -%} + +{%- block collection_widget -%} + {%- if prototype is defined and not prototype.rendered -%} + {%- set attr = attr|merge({'data-prototype': form_row(prototype)}) -%} + {%- endif -%} + {{- block('form_widget') -}} +{%- endblock collection_widget -%} + +{# ----- Rows ----------------------------------------------------------------------------------- #} + +{%- block form_row -%} + {%- set widget_attr = {} -%} + {%- set aria_describedby = [] -%} + {%- if help -%} + {%- set aria_describedby = aria_describedby|merge([id ~ '_help']) -%} + {%- endif -%} + {%- if errors|length > 0 -%} + {%- set aria_describedby = aria_describedby|merge([id ~ '_error']) -%} + {%- set widget_attr = widget_attr|merge({'aria-invalid': 'true'}) -%} + {%- endif -%} + {%- if aria_describedby|length > 0 and not compound -%} + {%- set widget_attr = widget_attr|merge({'aria-describedby': aria_describedby|join(' ')}) -%} + {%- endif -%} + {%- set row_attr = row_attr|merge({class: ('group/field flex w-full min-w-0 flex-col gap-2 ' ~ row_attr.class|default(''))|trim}) -%} + + {{- form_label(form) -}} + {{- form_widget(form, {attr: widget_attr}) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
+{%- endblock form_row -%} + +{%- block checkbox_row -%} + {%- set widget_attr = {} -%} + {%- if help -%} + {%- set widget_attr = widget_attr|merge({'aria-describedby': id ~ '_help'}) -%} + {%- endif -%} + {%- if errors|length > 0 -%} + {%- set widget_attr = widget_attr|merge({'aria-invalid': 'true'}) -%} + {%- endif -%} + {%- set row_attr = row_attr|merge({class: ('group/field flex w-full flex-col gap-1.5 ' ~ row_attr.class|default(''))|trim}) -%} + +
+ {{- form_widget(form, {attr: widget_attr}) -}} + {{- form_label(form) -}} +
+
+ {{- form_help(form) -}} + {{- form_errors(form) -}} +
+ +{%- endblock checkbox_row -%} + +{%- block radio_row -%} + {{- block('checkbox_row') -}} +{%- endblock radio_row -%} + +{%- block button_row -%} + + {{- form_widget(form) -}} + +{%- endblock button_row -%} + +{# ----- Labels, help, errors --------------------------------------------------------------- #} + +{%- block form_label -%} + {%- if label is not same as(false) -%} + {%- if not compound -%} + {%- set label_attr = label_attr|merge({for: id}) -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: ('flex w-fit items-center gap-1 text-sm leading-snug font-medium select-none group-data-[invalid=true]/field:text-destructive peer-disabled:cursor-not-allowed peer-disabled:opacity-50 ' ~ label_attr.class|default('')|replace({'checkbox-switch': ''}))|trim}) -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {%- set label = label_format|replace({'%name%': name, '%id%': id}) -%} + {%- else -%} + {%- set label = name|humanize -%} + {%- endif -%} + {%- endif -%} + <{{ element|default('label') }}{% if label_attr %}{% with {attr: label_attr} %}{{ block('attributes') }}{% endwith %}{% endif %} data-slot="field-label"> + {{- block('form_label_content') -}} + {%- if required and not ('checkbox' in block_prefixes or 'radio' in block_prefixes) -%} + + {%- endif -%} + + {%- endif -%} +{%- endblock form_label -%} + +{%- block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: ('text-start text-sm leading-normal font-normal text-muted-foreground ' ~ help_attr.class|default(''))|trim}) -%} +

+ {{- block('form_help_content') -}} +

+ {%- endif -%} +{%- endblock form_help -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} + {%- if form is rootform -%} + + {%- else -%} + + {%- endif -%} + {%- endif -%} +{%- endblock form_errors -%} + +{# ----- Widgets -------------------------------------------------------------------------------- #} + +{%- block form_widget_simple -%} + {%- if type|default('text') not in ['hidden', 'range', 'color'] -%} + {%- set attr = attr|merge({class: (block('shadcn_input_class') ~ ' ' ~ attr.class|default(''))|tailwind_merge}) -%} + {%- endif -%} + {%- if type|default('text') == 'range' -%} + {%- set attr = attr|merge({class: ('w-full accent-primary ' ~ attr.class|default(''))|trim}) -%} + {%- endif -%} + {{- parent() -}} +{%- endblock form_widget_simple -%} + +{%- block textarea_widget -%} + {%- set attr = attr|merge({class: (block('shadcn_textarea_class') ~ ' ' ~ attr.class|default(''))|tailwind_merge}) -%} + {{- parent() -}} +{%- endblock textarea_widget -%} + +{%- block choice_widget_collapsed -%} + {%- set is_autocomplete = 'autocomplete' in attr['data-controller']|default('') -%} + {%- if is_autocomplete -%} + {#- Tom Select copies the select classes onto its wrapper: the combobox is drawn by theme.css (.ts-*). -#} + {%- set attr = attr|merge({class: ('w-full ' ~ attr.class|default(''))|trim}) -%} + {{- parent() -}} + {%- elseif multiple -%} + {%- set attr = attr|merge({class: (block('shadcn_select_class') ~ ' h-auto min-h-16 pe-2.5 ' ~ attr.class|default(''))|tailwind_merge}) -%} + {{- parent() -}} + {%- else -%} + {%- set attr = attr|merge({class: (block('shadcn_select_class') ~ ' ' ~ attr.class|default(''))|tailwind_merge}) -%} +
+ {{- parent() -}} + {{ ux_icon('lucide:chevron-down', {class: 'pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground select-none', 'aria-hidden': 'true'}) }} +
+ {%- endif -%} +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_expanded -%} + {%- set attr = attr|merge({class: ('flex flex-col gap-3 ' ~ attr.class|default(''))|trim, role: multiple ? 'group' : 'radiogroup'}) -%} +
+ {%- for child in form -%} +
+ {{- form_widget(child) -}} + {{- form_label(child, null, {translation_domain: choice_translation_domain, label_attr: {class: 'font-normal'}}) -}} +
+ {%- endfor -%} +
+{%- endblock choice_widget_expanded -%} + +{%- block checkbox_widget -%} + {%- set is_switch = 'checkbox-switch' in label_attr.class|default('') or attr.role|default('') == 'switch' -%} + {%- if is_switch -%} + {%- set attr = attr|merge({role: 'switch', class: ('absolute inset-0 cursor-pointer appearance-none rounded-full focus:outline-hidden disabled:cursor-not-allowed ' ~ attr.class|default(''))|trim}) -%} + + + {{- parent() -}} + + {%- else -%} + {%- set attr = attr|merge({class: (block('shadcn_checkbox_class') ~ ' ' ~ attr.class|default(''))|tailwind_merge}) -%} + + {{- parent() -}} + + {{ ux_icon('lucide:check', {class: 'size-3.5', 'aria-hidden': 'true'}) }} + + + {%- endif -%} +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + {%- set attr = attr|merge({class: (block('shadcn_radio_class') ~ ' ' ~ attr.class|default(''))|tailwind_merge}) -%} + {{- parent() -}} +{%- endblock radio_widget -%} + +{%- block datetime_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: ('flex flex-wrap items-center gap-2 *:min-w-0 *:flex-1 ' ~ attr.class|default(''))|trim}) -%} +
+ {{- form_widget(form.date) -}} + {{- form_widget(form.time) -}} +
+ {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + {%- endif -%} +{%- endblock datetime_widget -%} + +{%- block date_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: ('flex items-center gap-2 *:min-w-0 *:flex-1 ' ~ attr.class|default(''))|trim}) -%} +
+ {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
+ {%- endif -%} +{%- endblock date_widget -%} + +{%- block time_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set vars = widget == 'text' ? {attr: {size: 1}} : {} -%} + {%- set attr = attr|merge({class: ('flex items-center gap-2 *:min-w-0 *:flex-1 ' ~ attr.class|default(''))|trim}) -%} +
+ {{ form_widget(form.hour, vars) }}{% if with_minutes %}{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}{{ form_widget(form.second, vars) }}{% endif %} +
+ {%- endif -%} +{%- endblock time_widget -%} + +{%- block money_widget -%} +
+ {{- money_pattern|form_encode_currency(block('form_widget_simple')) -}} +
+{%- endblock money_widget -%} + +{%- block percent_widget -%} + {%- if symbol -%} +
+ {%- set type = type|default('text') -%} + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget -%} + +{%- block button_widget -%} + {%- from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class -%} + {%- set attr = attr|merge({class: button_class('outline', 'default', attr.class|default(''))|trim}) -%} + {{- parent() -}} +{%- endblock button_widget -%} + +{%- block submit_widget -%} + {%- from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class -%} + {%- set type = type|default('submit') -%} + {%- set attr = attr|merge({class: button_class('default', 'default', attr.class|default(''))|trim}) -%} + {{- block('form_widget_button_base') -}} +{%- endblock submit_widget -%} + +{%- block form_widget_button_base -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({'%name%': name, '%id%': id}) %} + {%- elseif label is not same as(false) -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + +{%- endblock form_widget_button_base -%} diff --git a/src/ShadcnAdminUi/templates/shared/grid/action/apply_transition.html.twig b/src/ShadcnAdminUi/templates/shared/grid/action/apply_transition.html.twig new file mode 100644 index 00000000..df4a0381 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/action/apply_transition.html.twig @@ -0,0 +1,21 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + +{% if workflow_can(data, action.name) %} + {% if options.link.url is defined %} + {% set path = options.link.url %} + {% else %} + {% set path = path(options.link.route, options.link.parameters|default({id: data.id})) %} + {% endif %} + + {% set message = action.label|default('sylius.ui.apply_transition') %} + {% set variants = {danger: 'destructive', red: 'destructive', primary: 'default', green: 'secondary', success: 'secondary'} %} + +
+ + + +
+{% endif %} diff --git a/src/ShadcnAdminUi/templates/shared/grid/action/create.html.twig b/src/ShadcnAdminUi/templates/shared/grid/action/create.html.twig new file mode 100644 index 00000000..30dbd938 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/action/create.html.twig @@ -0,0 +1,9 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + +{% set path = options.link.url|default(path(options.link.route|default(grid.requestConfiguration.getRouteName('create')), options.link.parameters|default([]))) %} +{% set message = action.label is empty ? 'sylius.ui.create' : action.label %} + + + {{ ux_icon(action.icon|default('lucide:plus'), {'data-icon': 'inline-start', 'aria-hidden': 'true'}) }} + {{ message|trans }} + diff --git a/src/ShadcnAdminUi/templates/shared/grid/action/delete.html.twig b/src/ShadcnAdminUi/templates/shared/grid/action/delete.html.twig new file mode 100644 index 00000000..0a706852 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/action/delete.html.twig @@ -0,0 +1,16 @@ +{% set id = options.link.parameters['id']|default(data.id) %} + +{% if options.link.url is defined %} + {% set path = options.link.url %} +{% else %} + {% set default_route = options.link.route|default(grid.requestConfiguration is defined ? grid.requestConfiguration.getRouteName('delete') : null) %} + {% set path = default_route ? path(default_route, options.link.parameters|default({id: data.id})) : null %} +{% endif %} + +{{ component('sylius_shadcn_admin_ui:delete_modal', { + id: id, + path: path, + label: action.label|default('sylius.ui.delete'), + icon: action.icon|default('lucide:trash-2'), + disabled: path is empty, +}) }} diff --git a/src/ShadcnAdminUi/templates/shared/grid/action/show.html.twig b/src/ShadcnAdminUi/templates/shared/grid/action/show.html.twig new file mode 100644 index 00000000..6b41c883 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/action/show.html.twig @@ -0,0 +1,16 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + +{% if options.link.url is defined %} + {% set path = options.link.url %} +{% else %} + {% set default_route = options.link.route|default(grid.requestConfiguration is defined ? grid.requestConfiguration.getRouteName('show') : null) %} + {% set path = default_route ? path(default_route, options.link.parameters|default({id: data.id})) : null %} +{% endif %} + +{% set message = action.label|default('sylius.ui.show') %} +{% set disabled = path is empty %} + + + {{ ux_icon(action.icon|default('lucide:eye'), {'aria-hidden': 'true'}) }} + {{ message|trans }} + diff --git a/src/ShadcnAdminUi/templates/shared/grid/action/update.html.twig b/src/ShadcnAdminUi/templates/shared/grid/action/update.html.twig new file mode 100644 index 00000000..e1ae42f2 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/action/update.html.twig @@ -0,0 +1,16 @@ +{% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + +{% if options.link.url is defined %} + {% set path = options.link.url %} +{% else %} + {% set default_route = options.link.route|default(grid.requestConfiguration is defined ? grid.requestConfiguration.getRouteName('update') : null) %} + {% set path = default_route ? path(default_route, options.link.parameters|default({id: data.id})) : null %} +{% endif %} + +{% set message = action.label|default('sylius.ui.edit') %} +{% set disabled = path is empty %} + + + {{ ux_icon(action.icon|default('lucide:pencil'), {'aria-hidden': 'true'}) }} + {{ message|trans }} + diff --git a/src/ShadcnAdminUi/templates/shared/grid/bulk_action/delete.html.twig b/src/ShadcnAdminUi/templates/shared/grid/bulk_action/delete.html.twig new file mode 100644 index 00000000..b663a4db --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/bulk_action/delete.html.twig @@ -0,0 +1,24 @@ +{% set path = options.link.url|default(path(options.link.route|default(grid.requestConfiguration.getRouteName('bulk_delete')), options.link.parameters|default({}))) %} + + + + {% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + + + diff --git a/src/ShadcnAdminUi/templates/shared/grid/field/boolean.html.twig b/src/ShadcnAdminUi/templates/shared/grid/field/boolean.html.twig new file mode 100644 index 00000000..ddae603c --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/field/boolean.html.twig @@ -0,0 +1,3 @@ +{% import '@SyliusShadcnAdminUi/shared/helper/field/boolean.html.twig' as boolean %} + +{{ boolean.default(data) }} diff --git a/src/ShadcnAdminUi/templates/shared/grid/filter/boolean.html.twig b/src/ShadcnAdminUi/templates/shared/grid/filter/boolean.html.twig new file mode 100644 index 00000000..472fdc02 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/filter/boolean.html.twig @@ -0,0 +1,3 @@ +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + +{{ form_row(form, sylius_test_form_attribute('criterion-enabled')|sylius_merge_recursive({label: filter.label})) }} diff --git a/src/ShadcnAdminUi/templates/shared/grid/filter/date.html.twig b/src/ShadcnAdminUi/templates/shared/grid/filter/date.html.twig new file mode 100644 index 00000000..e46daace --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/filter/date.html.twig @@ -0,0 +1,11 @@ +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + +
+ {{ filter.label|trans }} + {% for bound in [form.from, form.to] %} +
+ {{ bound.vars.label|trans }} +
{{ form_widget(bound) }}
+
+ {% endfor %} +
diff --git a/src/ShadcnAdminUi/templates/shared/grid/filter/entity.html.twig b/src/ShadcnAdminUi/templates/shared/grid/filter/entity.html.twig new file mode 100644 index 00000000..9c59bb88 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/filter/entity.html.twig @@ -0,0 +1,3 @@ +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + +{{ form_row(form, {label: filter.label}) }} diff --git a/src/ShadcnAdminUi/templates/shared/grid/filter/enum.html.twig b/src/ShadcnAdminUi/templates/shared/grid/filter/enum.html.twig new file mode 100644 index 00000000..9c59bb88 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/filter/enum.html.twig @@ -0,0 +1,3 @@ +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + +{{ form_row(form, {label: filter.label}) }} diff --git a/src/ShadcnAdminUi/templates/shared/grid/filter/exists.html.twig b/src/ShadcnAdminUi/templates/shared/grid/filter/exists.html.twig new file mode 100644 index 00000000..9c59bb88 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/filter/exists.html.twig @@ -0,0 +1,3 @@ +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + +{{ form_row(form, {label: filter.label}) }} diff --git a/src/ShadcnAdminUi/templates/shared/grid/filter/select.html.twig b/src/ShadcnAdminUi/templates/shared/grid/filter/select.html.twig new file mode 100644 index 00000000..9c59bb88 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/filter/select.html.twig @@ -0,0 +1,3 @@ +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + +{{ form_row(form, {label: filter.label}) }} diff --git a/src/ShadcnAdminUi/templates/shared/grid/filter/string.html.twig b/src/ShadcnAdminUi/templates/shared/grid/filter/string.html.twig new file mode 100644 index 00000000..8e8a2d7b --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/grid/filter/string.html.twig @@ -0,0 +1,13 @@ +{% form_theme form '@SyliusShadcnAdminUi/shared/form_theme.html.twig' %} + +
+ +
+ {% if form.type is defined %} +
{{ form_widget(form.type, {attr: {'aria-label': 'sylius.ui.filter'|trans}}) }}
+ {% endif %} +
+ {{ form_widget(form.value, {attr: {placeholder: form.value.vars.label|trans}}) }} +
+
+
diff --git a/src/ShadcnAdminUi/templates/shared/helper/_checkbox.html.twig b/src/ShadcnAdminUi/templates/shared/helper/_checkbox.html.twig new file mode 100644 index 00000000..350d1629 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/_checkbox.html.twig @@ -0,0 +1,15 @@ +{# Markup of the shadcn Checkbox recipe, usable from macros and includes. #} +{% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import html_attributes %} + + + + {{ ux_icon('lucide:check', {class: 'size-3.5', 'aria-hidden': 'true'}) }} + + + diff --git a/src/ShadcnAdminUi/templates/shared/helper/accordion.html.twig b/src/ShadcnAdminUi/templates/shared/helper/accordion.html.twig new file mode 100644 index 00000000..ed1cea2f --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/accordion.html.twig @@ -0,0 +1,18 @@ +{# Same signature as the BootstrapAdminUi macro, based on the shadcn Collapsible controller. #} +{% macro accordion(items, open = false, auto_close = null, flush = false) %} +
+ {% for item in items %} + {% set item = {open: open, icon: null}|merge(item) %} +
+ +
+ {{ item.content|default('Content') }} +
+
+ {% endfor %} +
+{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/avatar.html.twig b/src/ShadcnAdminUi/templates/shared/helper/avatar.html.twig new file mode 100644 index 00000000..741ff087 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/avatar.html.twig @@ -0,0 +1,15 @@ +{% macro default(img, class, initials) %} + + {% if img %} + + {% else %} + + {% if initials %}{{ initials }}{% else %}{{ ux_icon('lucide:user', {class: 'size-4'}) }}{% endif %} + + {% endif %} + +{% endmacro %} + +{% macro small(img, class, initials) %} + {{ _self.default(img, 'size-6 ' ~ class, initials) }} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/breadcrumbs.html.twig b/src/ShadcnAdminUi/templates/shared/helper/breadcrumbs.html.twig new file mode 100644 index 00000000..657a6304 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/breadcrumbs.html.twig @@ -0,0 +1,22 @@ +{# Same signature as the BootstrapAdminUi macro: items = [{name, url?, active?}, ...] #} +{% macro breadcrumbs(items) %} + {% set items = items|filter(item => item is not empty) %} + +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/button.html.twig b/src/ShadcnAdminUi/templates/shared/helper/button.html.twig new file mode 100644 index 00000000..43a1903b --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/button.html.twig @@ -0,0 +1,77 @@ +{# + # Same macro API as @SyliusBootstrapAdminUi/shared/helper/button.html.twig, so your custom + # templates only need the namespace to be changed. + # + # Extra params: variant ('default'|'secondary'|'outline'|'ghost'|'destructive'|'link') + #} +{% macro default(params = {}) %} + {% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class, html_attributes %} + + {% set params = { + attr: null, + class: null, + text: 'Button', + disabled: false, + dropdown: false, + icon: null, + icon_class: null, + icon_only: false, + id: null, + size: null, + type: 'button', + url: null, + form: null, + variant: 'outline', + }|merge(params) %} + + {% set size = params.icon_only + ? (params.size == 'large' ? 'icon-lg' : (params.size == 'small' ? 'icon-sm' : 'icon')) + : (params.size == 'large' ? 'lg' : (params.size == 'small' ? 'sm' : 'default')) %} + {% set class = button_class(params.variant, size, params.class|default('')) %} + {% set icon_attributes = {class: params.icon_class|default(''), 'aria-hidden': 'true'}|merge(params.icon_only ? {} : {'data-icon': 'inline-start'}) %} + {% set icon = params.icon ? ux_icon(params.icon, icon_attributes) %} + {% set label = params.icon_only ? '' ~ params.text|e ~ '' : params.text|e %} + + {% if params.url %} + + {{- icon|raw }}{{ label|raw -}} + + {% else %} + + {% endif %} +{% endmacro %} + +{% macro primary(params = {}) %} + {{ _self.default({variant: 'default'}|merge(params)) }} +{% endmacro %} + +{% macro delete(params = {}) %} + {{ _self.default({text: 'sylius.ui.delete'|trans, variant: 'destructive'}|merge(params)) }} +{% endmacro %} + +{% macro create(params = {}) %} + {{ _self.primary({type: 'submit', text: 'sylius.ui.create'|trans}|merge(params)) }} +{% endmacro %} + +{% macro update(params = {}) %} + {{ _self.primary({type: 'submit', text: 'sylius.ui.update'|trans}|merge(params)) }} +{% endmacro %} + +{% macro cancel(params = {}) %} + {{ _self.default({text: 'sylius.ui.cancel'|trans, url: app.request.headers.get('referer'), variant: 'outline'}|merge(params)) }} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/dropdown.html.twig b/src/ShadcnAdminUi/templates/shared/helper/dropdown.html.twig new file mode 100644 index 00000000..fbb663bf --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/dropdown.html.twig @@ -0,0 +1,85 @@ +{# + # Same signature as the BootstrapAdminUi macros (default / list), rendered with the shadcn + # DropdownMenu controller. params.id makes the generated ids stable (random otherwise). + #} +{% macro default(params) %} + {% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + + {% set params = { + id: 'dropdown-' ~ random(), + button: {}, + class: null, + content: null, + customTrigger: null, + direction: 'down', + }|merge(params) %} + {% set trigger_id = 'dropdown-menu-' ~ params.id ~ '-trigger' %} + {% set content_id = 'dropdown-menu-' ~ params.id ~ '-content' %} + {% set align_class = params.direction == 'down-end' ? 'end-0 ltr:origin-top-right rtl:origin-top-left' : 'start-0 ltr:origin-top-left rtl:origin-top-right' %} + +
+ {% set trigger_attributes %} + id="{{ trigger_id }}" data-slot="dropdown-menu-trigger" data-shadcn--dropdown-menu-target="trigger" aria-haspopup="menu" aria-controls="{{ content_id }}" aria-expanded="false" data-state="closed" data-action="click->shadcn--dropdown-menu#toggle keydown->shadcn--dropdown-menu#onTriggerKeydown" + {% endset %} + {% if params.customTrigger %} +
{{ params.customTrigger }}
+ {% else %} + {% set button = {text: 'Menu', icon: null, class: null}|merge(params.button) %} + + {% endif %} + +
+{% endmacro %} + +{# content is printed raw: escape user data before passing it. #} +{% macro item(attributes = {}, content = '', variant = 'default') %} + {% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import html_attributes %} + {% set tag = attributes.href is defined ? 'a' : 'button' %} + <{{ tag }} + role="menuitem" + tabindex="-1" + data-slot="dropdown-menu-item" + data-variant="{{ variant }}" + data-shadcn--dropdown-menu-target="item" + data-close-on-select="true" + data-action="click->shadcn--dropdown-menu#closeFromItem" + {% if tag == 'button' %}type="button"{% endif %} + class="relative flex w-full cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden transition-colors select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4 {{ variant == 'destructive' ? 'text-destructive hover:bg-destructive/10 hover:text-destructive focus:bg-destructive/10 focus:text-destructive' : '' }}" + {{- html_attributes(attributes) }} + >{{ content|raw }} +{% endmacro %} + +{% macro list(params, items) %} + {% set content %} + {% for item in items %} + {% set item = {type: null, class: null, icon: null}|merge(item) %} + {% if item.type == 'header' %} +
{{ item.title|trans }}
+ {% elseif item.type == 'divider' %} + + {% else %} + {% set icon = item.icon ? ux_icon(item.icon, {'aria-hidden': 'true'}) : '' %} + {{ _self.item({href: item.url, class: item.class}, icon ~ (item.title|trans|e)) }} + {% endif %} + {% endfor %} + {% endset %} + + {{ _self.default(params|merge({content: content})) }} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/field/boolean.html.twig b/src/ShadcnAdminUi/templates/shared/helper/field/boolean.html.twig new file mode 100644 index 00000000..46100431 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/field/boolean.html.twig @@ -0,0 +1,13 @@ +{% macro default(data) %} + {% if true == data %} + + {{ ux_icon('lucide:circle-check', {class: 'size-4', 'aria-hidden': 'true'}) }} + {{ 'sylius.ui.yes_label'|trans }} + + {% else %} + + {{ ux_icon('lucide:circle-x', {class: 'size-4', 'aria-hidden': 'true'}) }} + {{ 'sylius.ui.no_label'|trans }} + + {% endif %} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/header.html.twig b/src/ShadcnAdminUi/templates/shared/helper/header.html.twig new file mode 100644 index 00000000..991c645e --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/header.html.twig @@ -0,0 +1,21 @@ +{% macro default(level, header, icon, subheader) %} +
+ {% if icon %} +
+ {{ ux_icon(icon, {class: 'size-5', 'aria-hidden': 'true'}) }} +
+ {% endif %} +
+ + {{ header }} + + {% if subheader %} +

{{ subheader }}

+ {% endif %} +
+
+{% endmacro %} + +{% macro h1(header, icon, subheader) %} + {{ _self.default(1, header, icon, subheader) }} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/pagination.html.twig b/src/ShadcnAdminUi/templates/shared/helper/pagination.html.twig new file mode 100644 index 00000000..8720828c --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/pagination.html.twig @@ -0,0 +1,43 @@ +{% macro simple(paginator, options = {}) %} + {{ pagerfanta(paginator, 'twig', {template: '@SyliusShadcnAdminUi/shared/pagerfanta.html.twig'}|merge(options)) }} +{% endmacro %} + +{% macro results_count(paginator) %} + {% set from = paginator.nbResults == 0 ? 0 : 1 + (paginator.currentPage - 1) * paginator.maxPerPage %} + {% set to = paginator.maxPerPage + (paginator.currentPage - 1) * paginator.maxPerPage %} + {% set to = to < paginator.nbResults ? to : paginator.nbResults %} + + {{ 'sylius.ui.pagination.number_of_results'|trans({'%from%': from, '%to%': to, '%total%': paginator.nbResults}) }} +{% endmacro %} + +{% macro number_of_results_selector(paginator, pagination_limits) %} + {% import '@SyliusShadcnAdminUi/shared/helper/dropdown.html.twig' as dropdown %} + {% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + + {% set other_pagination_limits = pagination_limits|filter(limit => limit != paginator.maxPerPage) %} + + {% if other_pagination_limits is not empty %} + {% set content %} + {% for limit in pagination_limits %} + {% set path = path(app.request.attributes.get('_route'), app.request.attributes.all('_route_params')|merge(app.request.query.all)|merge({limit: limit, page: 1})) %} + {% set label %} + {{ limit }} + {% if limit == paginator.maxPerPage %}{{ ux_icon('lucide:check', {'aria-hidden': 'true'}) }}{% endif %} + {% endset %} + {{ dropdown.item({href: path}, label) }} + {% endfor %} + {% endset %} + + {{ dropdown.default({ + id: 'grid-limit', + button: {text: 'sylius.ui.show'|trans ~ ' ' ~ paginator.maxPerPage, class: 'h-7 text-[0.8rem]'}, + content: content, + direction: 'down-end', + class: 'w-28', + }) }} + {% else %} + + {% endif %} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/palette.html.twig b/src/ShadcnAdminUi/templates/shared/helper/palette.html.twig new file mode 100644 index 00000000..9152493b --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/palette.html.twig @@ -0,0 +1,38 @@ +{# + Palette of tones: turns a color name (the historical Tabler palette, as returned by application + enums) into Tailwind classes. Classes are written in full: Tailwind does not detect a class built + by concatenation (never "bg-" ~ color in a template). Shades come from the --tone-* tokens of + assets/styles/theme.css. + + tone(color, kind) - kind: soft (tinted background + text), solid (filled background), text, dot, + hover (filled background on hover/focus of a "group/tile" parent), + alert (border + tinted background of a shadcn:Alert). + Unknown or empty color: neutral rendering (secondary). +#} +{% macro tone(color, kind = 'soft') -%} + {%- set palette = { + blue: {soft: 'bg-tone-blue/10 text-tone-blue dark:bg-tone-blue/15', solid: 'bg-tone-blue text-background', text: 'text-tone-blue', dot: 'bg-tone-blue', hover: 'group-hover/tile:bg-tone-blue group-hover/tile:text-background group-focus-visible/tile:bg-tone-blue group-focus-visible/tile:text-background', alert: 'border-tone-blue/30 bg-tone-blue/10 text-tone-blue dark:bg-tone-blue/15'}, + azure: {soft: 'bg-tone-azure/10 text-tone-azure dark:bg-tone-azure/15', solid: 'bg-tone-azure text-background', text: 'text-tone-azure', dot: 'bg-tone-azure', hover: 'group-hover/tile:bg-tone-azure group-hover/tile:text-background group-focus-visible/tile:bg-tone-azure group-focus-visible/tile:text-background', alert: 'border-tone-azure/30 bg-tone-azure/10 text-tone-azure dark:bg-tone-azure/15'}, + indigo: {soft: 'bg-tone-indigo/10 text-tone-indigo dark:bg-tone-indigo/15', solid: 'bg-tone-indigo text-background', text: 'text-tone-indigo', dot: 'bg-tone-indigo', hover: 'group-hover/tile:bg-tone-indigo group-hover/tile:text-background group-focus-visible/tile:bg-tone-indigo group-focus-visible/tile:text-background', alert: 'border-tone-indigo/30 bg-tone-indigo/10 text-tone-indigo dark:bg-tone-indigo/15'}, + purple: {soft: 'bg-tone-purple/10 text-tone-purple dark:bg-tone-purple/15', solid: 'bg-tone-purple text-background', text: 'text-tone-purple', dot: 'bg-tone-purple', hover: 'group-hover/tile:bg-tone-purple group-hover/tile:text-background group-focus-visible/tile:bg-tone-purple group-focus-visible/tile:text-background', alert: 'border-tone-purple/30 bg-tone-purple/10 text-tone-purple dark:bg-tone-purple/15'}, + pink: {soft: 'bg-tone-pink/10 text-tone-pink dark:bg-tone-pink/15', solid: 'bg-tone-pink text-background', text: 'text-tone-pink', dot: 'bg-tone-pink', hover: 'group-hover/tile:bg-tone-pink group-hover/tile:text-background group-focus-visible/tile:bg-tone-pink group-focus-visible/tile:text-background', alert: 'border-tone-pink/30 bg-tone-pink/10 text-tone-pink dark:bg-tone-pink/15'}, + red: {soft: 'bg-tone-red/10 text-tone-red dark:bg-tone-red/15', solid: 'bg-tone-red text-background', text: 'text-tone-red', dot: 'bg-tone-red', hover: 'group-hover/tile:bg-tone-red group-hover/tile:text-background group-focus-visible/tile:bg-tone-red group-focus-visible/tile:text-background', alert: 'border-tone-red/30 bg-tone-red/10 text-tone-red dark:bg-tone-red/15'}, + orange: {soft: 'bg-tone-orange/10 text-tone-orange dark:bg-tone-orange/15', solid: 'bg-tone-orange text-background', text: 'text-tone-orange', dot: 'bg-tone-orange', hover: 'group-hover/tile:bg-tone-orange group-hover/tile:text-background group-focus-visible/tile:bg-tone-orange group-focus-visible/tile:text-background', alert: 'border-tone-orange/30 bg-tone-orange/10 text-tone-orange dark:bg-tone-orange/15'}, + yellow: {soft: 'bg-tone-yellow/10 text-tone-yellow dark:bg-tone-yellow/15', solid: 'bg-tone-yellow text-background', text: 'text-tone-yellow', dot: 'bg-tone-yellow', hover: 'group-hover/tile:bg-tone-yellow group-hover/tile:text-background group-focus-visible/tile:bg-tone-yellow group-focus-visible/tile:text-background', alert: 'border-tone-yellow/30 bg-tone-yellow/10 text-tone-yellow dark:bg-tone-yellow/15'}, + lime: {soft: 'bg-tone-lime/10 text-tone-lime dark:bg-tone-lime/15', solid: 'bg-tone-lime text-background', text: 'text-tone-lime', dot: 'bg-tone-lime', hover: 'group-hover/tile:bg-tone-lime group-hover/tile:text-background group-focus-visible/tile:bg-tone-lime group-focus-visible/tile:text-background', alert: 'border-tone-lime/30 bg-tone-lime/10 text-tone-lime dark:bg-tone-lime/15'}, + green: {soft: 'bg-tone-green/10 text-tone-green dark:bg-tone-green/15', solid: 'bg-tone-green text-background', text: 'text-tone-green', dot: 'bg-tone-green', hover: 'group-hover/tile:bg-tone-green group-hover/tile:text-background group-focus-visible/tile:bg-tone-green group-focus-visible/tile:text-background', alert: 'border-tone-green/30 bg-tone-green/10 text-tone-green dark:bg-tone-green/15'}, + teal: {soft: 'bg-tone-teal/10 text-tone-teal dark:bg-tone-teal/15', solid: 'bg-tone-teal text-background', text: 'text-tone-teal', dot: 'bg-tone-teal', hover: 'group-hover/tile:bg-tone-teal group-hover/tile:text-background group-focus-visible/tile:bg-tone-teal group-focus-visible/tile:text-background', alert: 'border-tone-teal/30 bg-tone-teal/10 text-tone-teal dark:bg-tone-teal/15'}, + cyan: {soft: 'bg-tone-cyan/10 text-tone-cyan dark:bg-tone-cyan/15', solid: 'bg-tone-cyan text-background', text: 'text-tone-cyan', dot: 'bg-tone-cyan', hover: 'group-hover/tile:bg-tone-cyan group-hover/tile:text-background group-focus-visible/tile:bg-tone-cyan group-focus-visible/tile:text-background', alert: 'border-tone-cyan/30 bg-tone-cyan/10 text-tone-cyan dark:bg-tone-cyan/15'}, + success: {soft: 'bg-tone-green/10 text-tone-green dark:bg-tone-green/15', solid: 'bg-tone-green text-background', text: 'text-tone-green', dot: 'bg-tone-green', hover: 'group-hover/tile:bg-tone-green group-hover/tile:text-background group-focus-visible/tile:bg-tone-green group-focus-visible/tile:text-background', alert: 'border-tone-green/30 bg-tone-green/10 text-tone-green dark:bg-tone-green/15'}, + danger: {soft: 'bg-tone-red/10 text-tone-red dark:bg-tone-red/15', solid: 'bg-tone-red text-background', text: 'text-tone-red', dot: 'bg-tone-red', hover: 'group-hover/tile:bg-tone-red group-hover/tile:text-background group-focus-visible/tile:bg-tone-red group-focus-visible/tile:text-background', alert: 'border-tone-red/30 bg-tone-red/10 text-tone-red dark:bg-tone-red/15'}, + warning: {soft: 'bg-tone-yellow/10 text-tone-yellow dark:bg-tone-yellow/15', solid: 'bg-tone-yellow text-background', text: 'text-tone-yellow', dot: 'bg-tone-yellow', hover: 'group-hover/tile:bg-tone-yellow group-hover/tile:text-background group-focus-visible/tile:bg-tone-yellow group-focus-visible/tile:text-background', alert: 'border-tone-yellow/30 bg-tone-yellow/10 text-tone-yellow dark:bg-tone-yellow/15'}, + brown: {soft: 'bg-tone-orange/10 text-tone-orange dark:bg-tone-orange/15', solid: 'bg-tone-orange text-background', text: 'text-tone-orange', dot: 'bg-tone-orange', hover: 'group-hover/tile:bg-tone-orange group-hover/tile:text-background group-focus-visible/tile:bg-tone-orange group-focus-visible/tile:text-background', alert: 'border-tone-orange/30 bg-tone-orange/10 text-tone-orange dark:bg-tone-orange/15'}, + info: {soft: 'bg-tone-azure/10 text-tone-azure dark:bg-tone-azure/15', solid: 'bg-tone-azure text-background', text: 'text-tone-azure', dot: 'bg-tone-azure', hover: 'group-hover/tile:bg-tone-azure group-hover/tile:text-background group-focus-visible/tile:bg-tone-azure group-focus-visible/tile:text-background', alert: 'border-tone-azure/30 bg-tone-azure/10 text-tone-azure dark:bg-tone-azure/15'}, + secondary: {soft: 'bg-muted text-muted-foreground', solid: 'bg-muted-foreground text-background', text: 'text-muted-foreground', dot: 'bg-muted-foreground', hover: 'group-hover/tile:bg-muted-foreground group-hover/tile:text-background group-focus-visible/tile:bg-muted-foreground group-focus-visible/tile:text-background', alert: 'bg-muted text-foreground'}, + dark: {soft: 'bg-foreground/10 text-foreground', solid: 'bg-foreground text-background', text: 'text-foreground', dot: 'bg-foreground', hover: 'group-hover/tile:bg-foreground group-hover/tile:text-background group-focus-visible/tile:bg-foreground group-focus-visible/tile:text-background', alert: 'border-foreground/30 bg-foreground/5 text-foreground'}, + primary: {soft: 'bg-primary/10 text-primary', solid: 'bg-primary text-primary-foreground', text: 'text-primary', dot: 'bg-primary', hover: 'group-hover/tile:bg-primary group-hover/tile:text-primary-foreground group-focus-visible/tile:bg-primary group-focus-visible/tile:text-primary-foreground', alert: 'border-primary/30 bg-primary/5 text-primary'}, + } -%} + {%- set aliases = {muted: 'secondary', gray: 'secondary', grey: 'secondary', body: 'dark', black: 'dark'} -%} + {%- set color = aliases[color] ?? color -%} + {{- (palette[color] ?? palette.secondary)[kind] -}} +{%- endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/sorting.html.twig b/src/ShadcnAdminUi/templates/shared/helper/sorting.html.twig new file mode 100644 index 00000000..ef8bafe9 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/sorting.html.twig @@ -0,0 +1,29 @@ +{% macro table_header(grid, field, attributes) %} + {% set sorting_order = grid.getSortingOrder(field.name)|default('asc') %} + {% set is_sorted = grid.isSortedBy(field.name) %} + {% set next_order = is_sorted ? (sorting_order == 'desc' ? 'asc' : 'desc') : sorting_order %} + + + + {{ field.label|trans }} + {% if is_sorted %} + {{ ux_icon(sorting_order == 'desc' ? 'lucide:arrow-down' : 'lucide:arrow-up', {class: 'size-3.5', 'aria-hidden': 'true'}) }} + {% else %} + {{ ux_icon('lucide:chevrons-up-down', {class: 'size-3.5 opacity-60', 'aria-hidden': 'true'}) }} + {% endif %} + + +{% endmacro %} + +{% macro link(field_name, attributes, sorting_order, parameters) %} + {%- set params = attributes.all('_route_params')|merge(parameters) -%} + {%- set sorting = {sorting: {(field_name): (sorting_order)}} -%} + {{- path(attributes.get('_route'), params|merge(sorting)|merge({page: 1})) -}} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/table.html.twig b/src/ShadcnAdminUi/templates/shared/helper/table.html.twig new file mode 100644 index 00000000..ca1a2ec8 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/table.html.twig @@ -0,0 +1,108 @@ +{# Same macro API as BootstrapAdminUi (headers / row), with the shadcn Table classes. #} +{% macro headers(grid, definition, request_attributes) %} + {% import '@SyliusShadcnAdminUi/shared/helper/sorting.html.twig' as sorting %} + + {% if definition.actionGroups.bulk is defined and definition.getEnabledActions('bulk')|length > 0 %} + + {{ include('@SyliusShadcnAdminUi/shared/helper/_checkbox.html.twig', { + attributes: { + 'aria-label': 'sylius.ui.all'|trans, + 'data-admin--grid-selection-target': 'all', + 'data-action': 'admin--grid-selection#toggleAll', + }, + }, false) }} + + {% endif %} + + {% for field in definition.fields|sylius_sort_by('position') %} + {% if field.enabled %} + {% if field.isSortable %} + {{ sorting.table_header(grid, field, request_attributes) }} + {% else %} + {{ field.label|trans }} + {% endif %} + {% endif %} + {% endfor %} + + {% if definition.actionGroups.item is defined and definition.getEnabledActions('item')|length > 0 %} + + {{ 'sylius.ui.actions'|trans }} + + {% endif %} +{% endmacro %} + +{% macro row(grid, definition, row) %} + {% import '@SyliusShadcnAdminUi/shared/helper/dropdown.html.twig' as dropdown %} + {% from '@SyliusShadcnAdminUi/shared/helper/ui.html.twig' import button_class %} + + {# + Below md the row becomes a card (same markup, CSS only, so there is no duplicated markup or ids): + a 3-column grid (bulk checkbox | title | row actions) with the actions top-right and each cell as a "label … value" line. + A field can opt into a richer card with options.vars.mobile: + title (card heading, top-left), badge (short line under the title, e.g. a status), + meta (muted line without label), hidden (not shown on mobile). + #} + + {% if definition.actionGroups.bulk is defined and definition.getEnabledActions('bulk')|length > 0 and row.id is defined %} + + {{ include('@SyliusShadcnAdminUi/shared/helper/_checkbox.html.twig', { + attributes: { + value: row.id, + 'aria-label': row.id, + 'data-admin--grid-selection-target': 'item', + 'data-action': 'admin--grid-selection#refresh', + }, + }, false) }} + + {% endif %} + + {% for field in definition.enabledFields|sylius_sort_by('position') %} + {# vars.wrap: let a long-text column wrap instead of widening the table #} + {% set mobile_class = { + title: 'max-md:col-span-1 max-md:col-start-2 max-md:row-start-1 max-md:block max-md:text-start max-md:text-base max-md:font-medium max-md:before:content-none', + badge: 'max-md:order-1 max-md:col-span-2 max-md:col-start-2 max-md:block max-md:justify-self-start max-md:text-start max-md:before:content-none', + meta: 'max-md:order-2 max-md:col-span-2 max-md:col-start-2 max-md:block max-md:text-start max-md:text-muted-foreground max-md:before:content-none', + hidden: 'max-md:hidden', + }[field.options.vars.mobile|default('')] ?? 'max-md:order-3' %} + + {% if field.name == 'code' %} + {{ sylius_grid_render_field(grid, field, row) }} + {% else %} + {{ sylius_grid_render_field(grid, field, row) }} + {% endif %} + + {% endfor %} + + {% if definition.actionGroups.item is defined and definition.getEnabledActions('item')|length > 0 %} + +
+ {% for action in definition.getEnabledActions('item')|sylius_sort_by('position') %} + {{ sylius_grid_render_action(grid, action, row) }} + {% endfor %} + + {% if definition.actionGroups.subitem is defined and definition.getEnabledActions('subitem')|length > 0 %} + {% set subactions %} + {% for action in definition.getEnabledActions('subitem')|sylius_sort_by('position') %} + {{ sylius_grid_render_action(grid, action, row) }} + {% endfor %} + {% endset %} + {% if subactions|trim|length > 0 %} + {% set more_button %} + + {{ ux_icon('lucide:ellipsis', {'aria-hidden': 'true'}) }} + {{ 'sylius_shadcn_admin_ui.ui.more'|trans }} + + {% endset %} + {# Action templates render icon-only buttons (label in .sr-only): in the menu, stretch each trigger (the element carrying title) into a labelled item. #} + {% set menu_class = 'flex flex-col *:w-full [&_[title]]:h-auto [&_[title]]:w-full [&_[title]]:justify-start [&_[title]]:gap-2 [&_[title]]:px-1.5 [&_[title]]:py-1 [&_[title]]:font-normal [&_[title].text-muted-foreground]:text-popover-foreground [&_[title]_.sr-only]:not-sr-only [&_[title]_.sr-only]:whitespace-nowrap' %} + {{ dropdown.default({id: 'row-' ~ row.id|default(random()), customTrigger: more_button, content: subactions, direction: 'down-end', class: menu_class}) }} + {% else %} + {# Keep the menu slot so the other action icons stay aligned from row to row #} + + {% endif %} + {% endif %} +
+ + {% endif %} + +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/helper/ui.html.twig b/src/ShadcnAdminUi/templates/shared/helper/ui.html.twig new file mode 100644 index 00000000..6ce92730 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/helper/ui.html.twig @@ -0,0 +1,42 @@ +{# + # Class helpers for places where a Twig component cannot be used (KnpMenu / Pagerfanta block + # recursion, legacy macros...). They mirror the shadcn recipes: keep them in sync when you + # re-install a recipe with "ux:install". + #} +{% macro button_class(variant = 'default', size = 'default', extra = '') %} + {%- set style = html_cva( + base: "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/80', + outline: 'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50', + secondary: 'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground', + ghost: 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50', + destructive: 'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2', + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2', + icon: 'size-8', + 'icon-xs': "size-6 rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3", + 'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)]', + 'icon-lg': 'size-9', + }, + }, + ) -%} + {{- (style.apply({variant: variant, size: size}) ~ ' ' ~ extra)|tailwind_merge -}} +{% endmacro %} + +{% macro html_attributes(attr) %} + {%- if attr is iterable -%} + {%- for name, value in attr -%} + {%- if value is same as(true) %} {{ name }}{% elseif value is not same as(false) and value is not null %} {{ name }}="{{ value }}"{% endif -%} + {%- endfor -%} + {%- elseif attr -%} + {#- Legacy string attributes ("data-foo=bar"), written by template authors. -#} + {{ (' ' ~ attr)|raw }} + {%- endif -%} +{% endmacro %} diff --git a/src/ShadcnAdminUi/templates/shared/layout/javascripts.html.twig b/src/ShadcnAdminUi/templates/shared/layout/javascripts.html.twig new file mode 100644 index 00000000..f625fe39 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/layout/javascripts.html.twig @@ -0,0 +1,2 @@ +{# The importmap entrypoint that starts Stimulus (see README > Installation). #} +{{ importmap(hookable_metadata.configuration.entrypoint|default('admin')) }} diff --git a/src/ShadcnAdminUi/templates/shared/layout/metatags.html.twig b/src/ShadcnAdminUi/templates/shared/layout/metatags.html.twig new file mode 100644 index 00000000..e517c0b1 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/layout/metatags.html.twig @@ -0,0 +1 @@ + diff --git a/src/ShadcnAdminUi/templates/shared/layout/stylesheets.html.twig b/src/ShadcnAdminUi/templates/shared/layout/stylesheets.html.twig new file mode 100644 index 00000000..c4436e51 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/layout/stylesheets.html.twig @@ -0,0 +1,2 @@ +{# The Tailwind entry built by symfonycasts/tailwind-bundle (see README > Installation). #} + diff --git a/src/ShadcnAdminUi/templates/shared/layout/theme.html.twig b/src/ShadcnAdminUi/templates/shared/layout/theme.html.twig new file mode 100644 index 00000000..7d4292b8 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/layout/theme.html.twig @@ -0,0 +1,10 @@ +{# Applied before the stylesheet: no flash of the wrong theme. Same storage key as BootstrapAdminUi. #} + diff --git a/src/ShadcnAdminUi/templates/shared/layout/title.html.twig b/src/ShadcnAdminUi/templates/shared/layout/title.html.twig new file mode 100644 index 00000000..55d34523 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/layout/title.html.twig @@ -0,0 +1 @@ +{{ hookable_metadata.configuration.title|default('Sylius') }} diff --git a/src/ShadcnAdminUi/templates/shared/pagerfanta.html.twig b/src/ShadcnAdminUi/templates/shared/pagerfanta.html.twig new file mode 100644 index 00000000..baa6dac8 --- /dev/null +++ b/src/ShadcnAdminUi/templates/shared/pagerfanta.html.twig @@ -0,0 +1,56 @@ +{#- Pagerfanta view with the shadcn Pagination look. Plain HTML: Pagerfanta renders it block by block. -#} +{%- extends '@BabDevPagerfanta/default.html.twig' -%} + +{%- block pager_widget -%} + +{%- endblock pager_widget -%} + +{%- block page_link -%} +
  • {{- page -}}
  • +{%- endblock page_link -%} + +{%- block current_page_link -%} +
  • {{- page -}}
  • +{%- endblock current_page_link -%} + +{%- block previous_page_link -%} +
  • + +
  • +{%- endblock previous_page_link -%} + +{%- block previous_page_link_disabled -%} +
  • + + {{ ux_icon('lucide:chevron-left', {class: 'size-4 rtl:rotate-180', 'aria-hidden': 'true'}) }} + +
  • +{%- endblock previous_page_link_disabled -%} + +{%- block next_page_link -%} +
  • + +
  • +{%- endblock next_page_link -%} + +{%- block next_page_link_disabled -%} +
  • + + {{ ux_icon('lucide:chevron-right', {class: 'size-4 rtl:rotate-180', 'aria-hidden': 'true'}) }} + +
  • +{%- endblock next_page_link_disabled -%} + +{%- block ellipsis -%} + +{%- endblock ellipsis -%} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/.env b/src/ShadcnAdminUi/tests/Functional/.application/.env new file mode 100644 index 00000000..d84e5bf0 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/.env @@ -0,0 +1,24 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# https://symfony.com/doc/current/configuration/secrets.html +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=c9570eaa5cbfadf33ad1a93f95c2e42a +###< symfony/framework-bundle ### + +###> doctrine/doctrine-bundle ### +DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" +###< doctrine/doctrine-bundle ### diff --git a/src/ShadcnAdminUi/tests/Functional/.application/.gitignore b/src/ShadcnAdminUi/tests/Functional/.application/.gitignore new file mode 100644 index 00000000..79b02512 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/.gitignore @@ -0,0 +1,17 @@ + +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### + +composer.lock + +###> symfony/asset-mapper ### +/assets/vendor/ +/public/assets/ +###< symfony/asset-mapper ### diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/admin.js b/src/ShadcnAdminUi/tests/Functional/.application/assets/admin.js new file mode 100644 index 00000000..fea4166e --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/admin.js @@ -0,0 +1,4 @@ +// Admin entrypoint: starts Stimulus with the controllers of the demo and of the bundle. +import { startStimulusApp } from '@symfony/stimulus-bundle'; + +startStimulusApp(); diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/controllers.json b/src/ShadcnAdminUi/tests/Functional/.application/assets/controllers.json new file mode 100644 index 00000000..a1c6e90c --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/controllers.json @@ -0,0 +1,4 @@ +{ + "controllers": [], + "entrypoints": [] +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/controllers/csrf_protection_controller.js b/src/ShadcnAdminUi/tests/Functional/.application/assets/controllers/csrf_protection_controller.js new file mode 100644 index 00000000..b0849fde --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/controllers/csrf_protection_controller.js @@ -0,0 +1,85 @@ +const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; +const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; + +// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager +// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event +// and thus this event-listener will not be executed. +document.addEventListener('submit', function (event) { + generateCsrfToken(event.target); +}, true); + +// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie +// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked +document.addEventListener('turbo:submit-start', function (event) { + const h = generateCsrfHeaders(event.detail.formSubmission.formElement); + Object.keys(h).map(function (k) { + event.detail.formSubmission.fetchRequest.headers[k] = h[k]; + }); +}); + +// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted +document.addEventListener('turbo:submit-end', function (event) { + removeCsrfToken(event.detail.formSubmission.formElement); +}); + +export function generateCsrfToken (formElement) { + const csrfField = getCsrfField(formElement); + + if (!csrfField) { + return; + } + + let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + let csrfToken = csrfField.value; + + if (!csrfCookie && nameCheck.test(csrfToken)) { + csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken); + csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18)))); + } + csrfField.dispatchEvent(new Event('change', { bubbles: true })); + + if (csrfCookie && tokenCheck.test(csrfToken)) { + const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict'; + document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; + } +} + +export function generateCsrfHeaders (formElement) { + const headers = {}; + const csrfField = getCsrfField(formElement); + + if (!csrfField) { + return headers; + } + + const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + headers[csrfCookie] = csrfField.value; + } + + return headers; +} + +export function removeCsrfToken (formElement) { + const csrfField = getCsrfField(formElement); + + if (!csrfField) { + return; + } + + const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0'; + + document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; + } +} + +function getCsrfField (formElement) { + return Array.from(formElement.elements).find((field) => field.matches('input[data-controller="csrf-protection"], input[name="_csrf_token"]')); +} + +/* stimulusFetch: 'lazy' */ +export default 'csrf-protection-controller'; diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-down.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-down.svg new file mode 100644 index 00000000..39ad54dd --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right-left.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right-left.svg new file mode 100644 index 00000000..9ae6a934 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right.svg new file mode 100644 index 00000000..059d3940 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-up.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-up.svg new file mode 100644 index 00000000..86bcb102 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/arrow-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-copy.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-copy.svg new file mode 100644 index 00000000..ffc615b7 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-open.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-open.svg new file mode 100644 index 00000000..9edf0b71 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/book-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/check.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/check.svg new file mode 100644 index 00000000..008943df --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-down.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-down.svg new file mode 100644 index 00000000..3182a2a6 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-left.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-left.svg new file mode 100644 index 00000000..93f1e3c1 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-right.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-right.svg new file mode 100644 index 00000000..c2d3c5fd --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevrons-up-down.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevrons-up-down.svg new file mode 100644 index 00000000..de1adf56 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/chevrons-up-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-alert.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-alert.svg new file mode 100644 index 00000000..67c653e0 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-alert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-check.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-check.svg new file mode 100644 index 00000000..2c2d48aa --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-x.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-x.svg new file mode 100644 index 00000000..befa4f95 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/circle-x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/copy.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/copy.svg new file mode 100644 index 00000000..a9576857 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/ellipsis.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/ellipsis.svg new file mode 100644 index 00000000..17ef08c0 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/ellipsis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/external-link.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/external-link.svg new file mode 100644 index 00000000..fe374b16 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/eye.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/eye.svg new file mode 100644 index 00000000..f1c02a33 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/filter.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/filter.svg new file mode 100644 index 00000000..df172fa6 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/inbox.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/inbox.svg new file mode 100644 index 00000000..c0dcec51 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/inbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/info.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/info.svg new file mode 100644 index 00000000..db8abdfc --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-dashboard.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-dashboard.svg new file mode 100644 index 00000000..e22ea5ef --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-grid.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-grid.svg new file mode 100644 index 00000000..e1dae6cb --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/layout-grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/library.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/library.svg new file mode 100644 index 00000000..6c54487f --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/library.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/loader-2.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/loader-2.svg new file mode 100644 index 00000000..366b4a8a --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/loader-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/log-out.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/log-out.svg new file mode 100644 index 00000000..3fa1f9f7 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/log-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/menu.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/menu.svg new file mode 100644 index 00000000..a3ad7892 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/minus.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/minus.svg new file mode 100644 index 00000000..b41cbe69 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/moon.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/moon.svg new file mode 100644 index 00000000..fcc2a3aa --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/more-horizontal.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/more-horizontal.svg new file mode 100644 index 00000000..17ef08c0 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/more-horizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/panel-left.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/panel-left.svg new file mode 100644 index 00000000..d5679692 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/panel-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/pencil.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/pencil.svg new file mode 100644 index 00000000..48ee08b3 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/pencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/plus.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/plus.svg new file mode 100644 index 00000000..9b2ee5b2 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/rotate-ccw.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/rotate-ccw.svg new file mode 100644 index 00000000..d02d9d82 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/rotate-ccw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/save.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/save.svg new file mode 100644 index 00000000..504deb25 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/search.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/search.svg new file mode 100644 index 00000000..12725713 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/server.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/server.svg new file mode 100644 index 00000000..56bb1a93 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sliders-horizontal.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sliders-horizontal.svg new file mode 100644 index 00000000..68455252 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sliders-horizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sun.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sun.svg new file mode 100644 index 00000000..3ca22946 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/tags.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/tags.svg new file mode 100644 index 00000000..9147b401 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/tags.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/trash-2.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/trash-2.svg new file mode 100644 index 00000000..bf3d9589 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/trash-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/triangle-alert.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/triangle-alert.svg new file mode 100644 index 00000000..ae83c2ed --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/triangle-alert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/user.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/user.svg new file mode 100644 index 00000000..b29514ca --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/warehouse.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/warehouse.svg new file mode 100644 index 00000000..39b84966 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/warehouse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/x.svg b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/x.svg new file mode 100644 index 00000000..7e18c3f6 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/icons/lucide/x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/assets/styles/admin.css b/src/ShadcnAdminUi/tests/Functional/.application/assets/styles/admin.css new file mode 100644 index 00000000..a127d0db --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/assets/styles/admin.css @@ -0,0 +1,10 @@ +@import "tailwindcss"; +/* Theme of the bundle, from its sources (an application imports it from vendor/sylius/shadcn-admin-ui). */ +@import "../../../../../assets/styles/theme.css"; + +/* Templates of the demo (the theme already scans the bundle templates). */ +@source "../../templates"; + +/* Your own tokens / overrides go here, e.g.: +:root { --primary: oklch(0.55 0.2 260); --radius: 0.5rem; } +*/ diff --git a/src/ShadcnAdminUi/tests/Functional/.application/bin/console b/src/ShadcnAdminUi/tests/Functional/.application/bin/console new file mode 100644 index 00000000..fd85706d --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/bin/console @@ -0,0 +1,21 @@ +#!/usr/bin/env php + dirname(__DIR__), +]; + +if (file_exists(dirname(__DIR__, 4).'/vendor/autoload_runtime.php')) { + require_once dirname(__DIR__, 4).'/vendor/autoload_runtime.php'; +} else { + require_once dirname(__DIR__, 6).'/vendor/autoload_runtime.php'; +} + +return function (array $context) { + $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); + + return new Application($kernel); +}; diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/bootstrap.php b/src/ShadcnAdminUi/tests/Functional/.application/config/bootstrap.php new file mode 100644 index 00000000..d9ed1c77 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/bootstrap.php @@ -0,0 +1,36 @@ +=1.2) +if (is_array($env = @include dirname(__DIR__) . '/.env.local.php')) { + $_SERVER += $env; + $_ENV += $env; +} elseif (!class_exists(Dotenv::class)) { + throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); +} else { + // load all the .env files + (new Dotenv())->loadEnv(dirname(__DIR__) . '/.env'); +} + +$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; +$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; +$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], \FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/bundles.php b/src/ShadcnAdminUi/tests/Functional/.application/config/bundles.php new file mode 100644 index 00000000..745637df --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/bundles.php @@ -0,0 +1,34 @@ + ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], + Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true], + Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], + Sylius\TwigHooks\SyliusTwigHooksBundle::class => ['all' => true], + BabDev\PagerfantaBundle\BabDevPagerfantaBundle::class => ['all' => true], + Sylius\Bundle\ResourceBundle\SyliusResourceBundle::class => ['all' => true], + Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true], + Sylius\AdminUi\Symfony\SyliusAdminUiBundle::class => ['all' => true], + Sylius\Bundle\GridBundle\SyliusGridBundle::class => ['all' => true], + Sylius\TwigExtra\Symfony\SyliusTwigExtraBundle::class => ['all' => true], + Sylius\UiTranslations\Symfony\SyliusUiTranslationsBundle::class => ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], + Symfony\UX\Icons\UXIconsBundle::class => ['all' => true], + Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true], + TalesFromADev\Twig\Extra\Tailwind\Bridge\Symfony\Bundle\TalesFromADevTwigExtraTailwindBundle::class => ['all' => true], + Sylius\ShadcnAdminUi\Symfony\SyliusShadcnAdminUiBundle::class => ['all' => true], +]; diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/asset_mapper.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/asset_mapper.yaml new file mode 100644 index 00000000..ca42f756 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/asset_mapper.yaml @@ -0,0 +1,6 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/csrf.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/csrf.yaml new file mode 100644 index 00000000..c8cbdbd0 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/csrf.yaml @@ -0,0 +1,7 @@ +framework: + form: + csrf_protection: + enabled: true + + csrf_protection: + enabled: true diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/doctrine.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/doctrine.yaml new file mode 100644 index 00000000..ae412739 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/doctrine.yaml @@ -0,0 +1,15 @@ +doctrine: + dbal: + # SQLite: no database server, one file per environment. + url: 'sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db' + profiling_collect_backtrace: '%kernel.debug%' + orm: + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore + auto_mapping: true + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'TestApplication\Sylius\ShadcnAdminUi\Entity' diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/framework.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/framework.yaml new file mode 100644 index 00000000..5f873407 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/framework.yaml @@ -0,0 +1,11 @@ +framework: + secret: 'Three can keep a secret, if two of them are dead.' + + # Note that the session will be started ONLY if you read or write from it. + session: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/routing.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/routing.yaml new file mode 100644 index 00000000..4142bbe6 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/routing.yaml @@ -0,0 +1,4 @@ +framework: + router: + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + default_uri: 'http://localhost:8080' diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/security.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/security.yaml new file mode 100644 index 00000000..bbe01509 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/security.yaml @@ -0,0 +1,29 @@ +security: + password_hashers: + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'plaintext' + providers: + users_in_memory: + memory: + users: + admin: { password: 'admin', roles: ['ROLE_ADMIN'] } + firewalls: + dev: + pattern: ^/(_profiler(/|$)|(_wdt|assets|build)/) + security: false + main: + lazy: true + provider: users_in_memory + form_login: + login_path: sylius_admin_ui_login + check_path: sylius_admin_ui_login_check + default_target_path: sylius_admin_ui_dashboard + enable_csrf: true + csrf_parameter: _csrf_admin_security_token + csrf_token_id: admin_authenticate + logout: + path: sylius_admin_ui_logout + target: sylius_admin_ui_login + access_control: + - { path: ^/admin/login, roles: PUBLIC_ACCESS } + - { path: ^/admin/logout, roles: PUBLIC_ACCESS } + - { path: ^/admin, roles: ROLE_ADMIN } diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_resource.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_resource.yaml new file mode 100644 index 00000000..f38fbf89 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_resource.yaml @@ -0,0 +1,15 @@ +# @see https://stack.sylius.com/resource/index +sylius_resource: + # Override default settings + #settings: + + # Configure the mapping for your resources + mapping: + paths: + - '%kernel.project_dir%/src/Entity' + + # Configure your resources + resources: + #app.book: + #classes: + #model: TestApplication\Sylius\ShadcnAdminUi\Entity\Book diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_shadcn_admin_ui.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_shadcn_admin_ui.yaml new file mode 100644 index 00000000..f96821d0 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_shadcn_admin_ui.yaml @@ -0,0 +1,8 @@ +# Configuration of the theme (hooks, grid templates, component prefixes). +# Loaded before sylius_twig_hooks.yaml (alphabetical order): the hooks of the application win. +imports: + - { resource: '@SyliusShadcnAdminUiBundle/config/app.php' } + +sylius_shadcn_admin_ui: + sidebar: + variant: floating diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_twig_hooks.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_twig_hooks.yaml new file mode 100644 index 00000000..a003729e --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/sylius_twig_hooks.yaml @@ -0,0 +1,51 @@ +# Customization of the theme by the application. +# Hook names are the Sylius Stack ones: php tests/Application/bin/console sylius:debug:twig-hooks +sylius_twig_hooks: + hooks: + # Title of the browser tab + 'sylius_admin.base#base_title': + default: + configuration: + title: 'Bibliothèque' + + # Sidebar logo + 'sylius_admin.common.component.sidebar.logo': + image: + configuration: + name: 'Bibliothèque' + subtitle: 'Démo Sylius Stack' + icon: 'lucide:library' + + # Login page logo + 'sylius_admin.security.login.page.logo': + image: + configuration: + name: 'Bibliothèque' + icon: 'lucide:library' + + # Dashboard: subtitle + statistics cards (TestApplication\Sylius\ShadcnAdminUi\Twig\Components\DashboardStats component) + 'sylius_admin.dashboard.index.content.header.title_block': + title: + configuration: + subheader: 'app.ui.dashboard_subtitle' + 'sylius_admin.dashboard.index.content': + stats: + template: 'admin/dashboard/stats.html.twig' + priority: -10 + + # "book" resource: the sylius_admin.book.* hooks complete or override + # the common sylius_admin.common.* hooks provided by the theme. + # To override an existing hookable, reuse its template (see sylius:debug:twig-hooks). + 'sylius_admin.book.show.content': + details: + template: 'admin/book/show/details.html.twig' + 'sylius_admin.book.show.content.header': + breadcrumbs: + template: '@SyliusShadcnAdminUi/shared/crud/show/content/header/breadcrumbs.html.twig' + configuration: + rendered_field: title + 'sylius_admin.book.update.content.header': + breadcrumbs: + template: '@SyliusShadcnAdminUi/shared/crud/update/content/header/breadcrumbs.html.twig' + configuration: + rendered_field: title diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/symfonycasts_tailwind.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/symfonycasts_tailwind.yaml new file mode 100644 index 00000000..957e1a8e --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/symfonycasts_tailwind.yaml @@ -0,0 +1,10 @@ +symfonycasts_tailwind: + binary_version: 'v4.3.3' + input_css: + # Stylesheet of the admin panel (imports the theme of the bundle). + - '%kernel.project_dir%/assets/styles/admin.css' + +when@test: + symfonycasts_tailwind: + # Tests render the pages without building the stylesheet. + strict_mode: false diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/translation.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/translation.yaml new file mode 100644 index 00000000..1ca9555e --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/translation.yaml @@ -0,0 +1,5 @@ +framework: + default_locale: fr + translator: + default_path: '%kernel.project_dir%/translations' + providers: diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig.yaml new file mode 100644 index 00000000..3f795d92 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig_component.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig_component.yaml new file mode 100644 index 00000000..1030f271 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/twig_component.yaml @@ -0,0 +1,5 @@ +twig_component: + anonymous_template_directory: 'components/' + defaults: + # Namespace & directory for components + TestApplication\Sylius\ShadcnAdminUi\Twig\Components\: 'components/' diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/ux_icons.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/ux_icons.yaml new file mode 100644 index 00000000..9abe1c2e --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/ux_icons.yaml @@ -0,0 +1,27 @@ +# Full configuration reference: https://symfony.com/bundles/ux-icons/current/index.html#configuration +ux_icons: + + # Default HTML attributes to add to all icons + default_icon_attributes: + + # Use current text color to fill the icon + fill: currentColor + + # Default dimensions + height: '1em' + width: '1em' + + # Throw an exception if an icon is not + ignore_not_found: false + + +when@prod: + ux_icons: + # Suppress exceptions for missing icons in production + ignore_not_found: true + +when@test: + ux_icons: + # No call to the Iconify API from the tests: every icon must be locked in assets/icons/. + iconify: + enabled: false diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/packages/validator.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/validator.yaml new file mode 100644 index 00000000..4015cd6e --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/packages/validator.yaml @@ -0,0 +1,11 @@ +framework: + validation: + # Enables validator auto-mapping support. + # For instance, basic validation constraints will be inferred from Doctrine's metadata. + #auto_mapping: + # TestApplication\Sylius\ShadcnAdminUi\Entity\: [] + +when@test: + framework: + validation: + not_compromised_password: false diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/preload.php b/src/ShadcnAdminUi/tests/Functional/.application/config/preload.php new file mode 100644 index 00000000..77c80b9e --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/preload.php @@ -0,0 +1,16 @@ + [ + * 'App\\' => [ + * 'resource' => '../src/', + * ], + * ], + * ]); + * ``` + * + * @psalm-type ImportsConfig = list + * @psalm-type ParametersConfig = array|Param|null>|Param|null> + * @psalm-type ArgumentsType = list|array + * @psalm-type CallType = array|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool} + * @psalm-type TagsType = list>> // arrays inside the list must have only one element, with the tag name as the key + * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator + * @psalm-type DeprecationType = array{package: string, version: string, message?: string} + * @psalm-type DefaultsType = array{ + * public?: bool, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * } + * @psalm-type InstanceofType = array{ + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type DefinitionType = array{ + * class?: string, + * file?: string, + * parent?: string, + * shared?: bool, + * synthetic?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * configurator?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * decorates?: string, + * decorates_tag?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * from_callable?: CallbackType, + * } + * @psalm-type AliasType = string|array{ + * alias: string, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type PrototypeType = array{ + * resource: string, + * namespace?: string, + * exclude?: string|list, + * parent?: string, + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type StackType = array{ + * stack: list>, + * public?: bool, + * deprecated?: DeprecationType, + * decorates?: string, + * decorates_tag?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, + * } + * @psalm-type ServicesConfig = array{ + * _defaults?: DefaultsType, + * _instanceof?: array, + * ... + * } + * @psalm-type ExtensionType = array + * @psalm-type FrameworkConfig = array{ + * secret?: scalar|Param|null, + * http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false + * allowed_http_method_override?: null|list, + * trust_x_sendfile_type_header?: scalar|Param|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" + * ide?: scalar|Param|null, // Default: "%env(default::SYMFONY_IDE)%" + * test?: bool|Param, + * default_locale?: scalar|Param|null, // Default: "en" + * set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false + * set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false + * enabled_locales?: list, + * trusted_hosts?: Param|string|list, + * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] + * trusted_headers?: Param|string|list, + * error_controller?: scalar|Param|null, // Default: "error_controller" + * handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|Param|null, // Default: null + * stateless_token_ids?: list, + * check_header?: scalar|Param|null, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false + * cookie_name?: scalar|Param|null, // The name of the cookie to use when using stateless protection. // Default: "csrf-token" + * }, + * form?: bool|array{ // Form configuration + * enabled?: bool|Param, // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|Param|null, // Default: null + * token_id?: scalar|Param|null, // Default: null + * field_name?: scalar|Param|null, // Default: "_token" + * field_attr?: array, + * }, + * }, + * http_cache?: bool|array{ // HTTP cache configuration + * enabled?: bool|Param, // Default: false + * debug?: bool|Param, // Default: "%kernel.debug%" + * trace_level?: "none"|"short"|"full"|Param, + * trace_header?: scalar|Param|null, + * default_ttl?: int|Param, + * private_headers?: list, + * skip_response_headers?: list, + * allow_reload?: bool|Param, + * allow_revalidate?: bool|Param, + * stale_while_revalidate?: int|Param, + * stale_if_error?: int|Param, + * terminate_on_cache_hit?: bool|Param, // Deprecated: Setting the "framework.http_cache.terminate_on_cache_hit.terminate_on_cache_hit" configuration option is deprecated. It will be removed in version 9.0. + * }, + * esi?: bool|array{ // ESI configuration + * enabled?: bool|Param, // Default: false + * }, + * ssi?: bool|array{ // SSI configuration + * enabled?: bool|Param, // Default: false + * }, + * fragments?: bool|array{ // Fragments configuration + * enabled?: bool|Param, // Default: false + * hinclude_default_template?: scalar|Param|null, // Default: null + * path?: scalar|Param|null, // Default: "/_fragment" + * }, + * profiler?: bool|array{ // Profiler configuration + * enabled?: bool|Param, // Default: false + * collect?: bool|Param, // Default: true + * collect_parameter?: scalar|Param|null, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null + * only_exceptions?: bool|Param, // Default: false + * only_main_requests?: bool|Param, // Default: false + * dsn?: scalar|Param|null, // Default: "file:%kernel.cache_dir%/profiler" + * collect_serializer_data?: true|Param, // Deprecated: Setting the "framework.profiler.collect_serializer_data.collect_serializer_data" configuration option is deprecated. It will be removed in version 9.0. // Default: true + * }, + * workflows?: bool|array{ + * enabled?: bool|Param, // Default: false + * workflows?: array, + * definition_validators?: list, + * support_strategy?: scalar|Param|null, + * initial_marking?: \BackedEnum|Param|string|list, + * events_to_dispatch?: null|list, + * places?: Param|string|list, + * }>, + * transitions?: list, + * to?: \BackedEnum|Param|string|list, + * weight?: int|Param, // Default: 1 + * metadata?: array, + * }>, + * metadata?: array, + * }>, + * }, + * router?: bool|array{ // Router configuration + * enabled?: bool|Param, // Default: false + * resource?: scalar|Param|null, + * type?: scalar|Param|null, + * default_uri?: scalar|Param|null, // The default URI used to generate URLs in a non-HTTP context. // Default: null + * http_port?: scalar|Param|null, // Default: 80 + * https_port?: scalar|Param|null, // Default: 443 + * strict_requirements?: scalar|Param|null, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true + * utf8?: bool|Param, // Default: true + * }, + * session?: bool|array{ // Session configuration + * enabled?: bool|Param, // Default: false + * storage_factory_id?: scalar|Param|null, // Default: "session.storage.factory.native" + * handler_id?: scalar|Param|null, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. + * name?: scalar|Param|null, + * cookie_lifetime?: scalar|Param|null, + * cookie_path?: scalar|Param|null, + * cookie_domain?: scalar|Param|null, + * cookie_secure?: true|false|"auto"|Param, // Default: "auto" + * cookie_httponly?: bool|Param, // Default: true + * cookie_samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax" + * use_cookies?: bool|Param, + * gc_divisor?: scalar|Param|null, + * gc_probability?: scalar|Param|null, + * gc_maxlifetime?: scalar|Param|null, + * save_path?: scalar|Param|null, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. + * metadata_update_threshold?: int|Param, // Seconds to wait between 2 session metadata updates. // Default: 0 + * }, + * request?: bool|array{ // Request configuration + * enabled?: bool|Param, // Default: false + * formats?: array>, + * }, + * assets?: bool|array{ // Assets configuration + * enabled?: bool|Param, // Default: true + * strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false + * version_strategy?: scalar|Param|null, // Default: null + * version?: scalar|Param|null, // Default: null + * version_format?: scalar|Param|null, // Default: "%%s?%%s" + * json_manifest_path?: scalar|Param|null, // Default: null + * base_path?: scalar|Param|null, // Default: "" + * base_urls?: Param|string|list, + * packages?: array, + * }>, + * }, + * asset_mapper?: bool|array{ // Asset Mapper configuration + * enabled?: bool|Param, // Default: true + * paths?: Param|string|array, + * excluded_patterns?: list, + * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true + * server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true + * public_prefix?: scalar|Param|null, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/" + * missing_import_mode?: "strict"|"warn"|"ignore"|Param, // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn" + * extensions?: array, + * importmap_path?: scalar|Param|null, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php" + * importmap_polyfill?: scalar|Param|null, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims" + * importmap_script_attributes?: array, + * vendor_dir?: scalar|Param|null, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor" + * precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip. + * enabled?: bool|Param, // Default: false + * formats?: list, + * extensions?: list, + * }, + * }, + * translator?: bool|array{ // Translator configuration + * enabled?: bool|Param, // Default: true + * fallbacks?: Param|string|list, + * logging?: bool|Param, // Default: false + * formatter?: scalar|Param|null, // Default: "translator.formatter.default" + * cache_dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/translations" + * default_path?: scalar|Param|null, // The default path used to load translations. // Default: "%kernel.project_dir%/translations" + * paths?: list, + * pseudo_localization?: bool|array{ + * enabled?: bool|Param, // Default: false + * accents?: bool|Param, // Default: true + * expansion_factor?: float|Param, // Default: 1.0 + * brackets?: bool|Param, // Default: true + * parse_html?: bool|Param, // Default: false + * localizable_html_attributes?: list, + * }, + * providers?: array, + * locales?: list, + * }>, + * globals?: array, + * domain?: string|Param, + * }>, + * }, + * validation?: bool|array{ // Validation configuration + * enabled?: bool|Param, // Default: true + * enable_attributes?: bool|Param, // Default: true + * static_method?: Param|string|list, + * translation_domain?: scalar|Param|null, // Default: "validators" + * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|Param, // Default: "html5" + * mapping?: array{ + * paths?: list, + * }, + * not_compromised_password?: bool|array{ + * enabled?: bool|Param, // When disabled, compromised passwords will be accepted as valid. // Default: true + * endpoint?: scalar|Param|null, // API endpoint for the NotCompromisedPassword Validator. // Default: null + * }, + * disable_translation?: bool|Param, // Default: false + * property_metadata_existence_check?: bool|Param, // When enabled, validateProperty() and validatePropertyValue() throw an exception if no metadata is found for the given property. // Default: false + * auto_mapping?: array, + * }>, + * }, + * serializer?: bool|array{ // Serializer configuration + * enabled?: bool|Param, // Default: false + * enable_attributes?: bool|Param, // Default: true + * name_converter?: scalar|Param|null, + * circular_reference_handler?: scalar|Param|null, + * max_depth_handler?: scalar|Param|null, + * mapping?: array{ + * paths?: list, + * }, + * default_context?: array, + * named_serializers?: array, + * include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true + * include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true + * }>, + * }, + * property_access?: bool|array{ // Property access configuration + * enabled?: bool|Param, // Default: true + * magic_call?: bool|Param, // Default: false + * magic_get?: bool|Param, // Default: true + * magic_set?: bool|Param, // Default: true + * throw_exception_on_invalid_index?: bool|Param, // Default: false + * throw_exception_on_invalid_property_path?: bool|Param, // Default: true + * }, + * type_info?: bool|array{ // Type info configuration + * enabled?: bool|Param, // Default: true + * aliases?: array, + * }, + * property_info?: bool|array{ // Property info configuration + * enabled?: bool|Param, // Default: true + * with_constructor_extractor?: bool|Param, // Registers the constructor extractor. // Default: true + * }, + * cache?: array{ // Cache configuration + * prefix_seed?: scalar|Param|null, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" + * app?: scalar|Param|null, // App related cache pools configuration. // Default: "cache.adapter.filesystem" + * system?: scalar|Param|null, // System related cache pools configuration. // Default: "cache.adapter.system" + * directory?: scalar|Param|null, // Default: "%kernel.share_dir%/pools/app" + * default_psr6_provider?: scalar|Param|null, + * default_redis_provider?: scalar|Param|null, // Default: "redis://localhost" + * default_valkey_provider?: scalar|Param|null, // Default: "valkey://localhost" + * default_memcached_provider?: scalar|Param|null, // Default: "memcached://localhost" + * default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection" + * default_pdo_provider?: scalar|Param|null, // Default: null + * pools?: array, + * tags?: scalar|Param|null, // Default: null + * public?: bool|Param, // Default: false + * default_lifetime?: scalar|Param|null, // Default lifetime of the pool. + * provider?: scalar|Param|null, // Overwrite the setting from the default provider for this adapter. + * early_expiration_message_bus?: scalar|Param|null, + * clearer?: scalar|Param|null, + * marshaller?: scalar|Param|null, // The marshaller service to use for this pool. + * }>, + * }, + * php_errors?: array{ // PHP errors handling configuration + * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true + * throw?: bool|Param, // Throw PHP errors as \ErrorException instances. // Default: true + * }, + * exceptions?: array, + * web_link?: bool|array{ // Web links configuration + * enabled?: bool|Param, // Default: false + * }, + * lock?: Param|bool|string|array{ // Lock configuration + * enabled?: bool|Param, // Default: false + * resources?: Param|string|array>, + * }, + * semaphore?: Param|bool|string|array{ // Semaphore configuration + * enabled?: bool|Param, // Default: false + * resources?: Param|string|array, + * }, + * messenger?: bool|array{ // Messenger configuration + * enabled?: bool|Param, // Default: false + * routing?: array>, + * serializer?: array{ + * default_serializer?: scalar|Param|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" + * symfony_serializer?: array{ + * format?: scalar|Param|null, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json" + * context?: array, + * }, + * }, + * transports?: array, + * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * retry_strategy?: Param|string|array{ + * service?: scalar|Param|null, // Service id to override the retry strategy entirely. // Default: null + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 + * }, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null + * }>, + * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * stop_worker_on_signals?: Param|int|string|list, + * default_bus?: scalar|Param|null, // Default: null + * buses?: array, + * }>, + * }>, + * }, + * scheduler?: bool|array{ // Scheduler configuration + * enabled?: bool|Param, // Default: false + * }, + * disallow_search_engine_index?: bool|Param, // Enabled by default when debug is enabled. // Default: true + * http_client?: bool|array{ // HTTP Client configuration + * enabled?: bool|Param, // Default: true + * max_host_connections?: int|Param, // The maximum number of connections to a single host. + * default_options?: array{ + * headers?: array, + * vars?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|Param|null, // A certificate authority file. + * capath?: scalar|Param|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|Param|null, // A PEM formatted certificate file. + * local_pk?: scalar|Param|null, // A private key file. + * passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. // Default: 86400 + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null + * http_codes?: Param|int|string|array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }, + * mock_response_factory?: scalar|Param|null, // `true` to always return empty 200 responses, or the id of the service to use to generate mock responses - which should be either an invokable or an iterable. + * scoped_clients?: array, + * headers?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|Param|null, // A certificate authority file. + * capath?: scalar|Param|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|Param|null, // A PEM formatted certificate file. + * local_pk?: scalar|Param|null, // A private key file. + * passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * mock_response_factory?: scalar|Param|null, // `true` to always return empty 200 responses, `false` to disable mocking, or the id of the service to use to generate mock responses (invokable or iterable). + * extra?: array, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. // Default: 86400 + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null + * http_codes?: Param|int|string|array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }>, + * }, + * mailer?: bool|array{ // Mailer configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|Param|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * dsn?: scalar|Param|null, // Default: null + * transports?: array, + * envelope?: array{ // Mailer Envelope configuration + * sender?: scalar|Param|null, + * recipients?: Param|string|list, + * allowed_recipients?: Param|string|list, + * }, + * headers?: array, + * dkim_signer?: bool|array{ // DKIM signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|Param|null, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" + * domain?: scalar|Param|null, // Default: "" + * select?: scalar|Param|null, // Default: "" + * passphrase?: scalar|Param|null, // The private key passphrase // Default: "" + * options?: array, + * }, + * smime_signer?: bool|array{ // S/MIME signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|Param|null, // Path to key (in PEM format) // Default: "" + * certificate?: scalar|Param|null, // Path to certificate (in PEM format without the `file://` prefix) // Default: "" + * passphrase?: scalar|Param|null, // The private key passphrase // Default: null + * extra_certificates?: scalar|Param|null, // Default: null + * sign_options?: int|Param, // Default: null + * }, + * smime_encrypter?: bool|array{ // S/MIME encrypter configuration + * enabled?: bool|Param, // Default: false + * repository?: scalar|Param|null, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: "" + * cipher?: int|Param, // A set of algorithms used to encrypt the message // Default: null + * }, + * }, + * secrets?: bool|array{ + * enabled?: bool|Param, // Default: true + * vault_directory?: scalar|Param|null, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%" + * local_dotenv_file?: scalar|Param|null, // Default: "%kernel.project_dir%/.env.%kernel.environment%.local" + * decryption_env_var?: scalar|Param|null, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET" + * }, + * notifier?: bool|array{ // Notifier configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|Param|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * chatter_transports?: array, + * texter_transports?: array, + * notification_on_failed_messages?: bool|Param, // Default: false + * channel_policy?: array>, + * admin_recipients?: list, + * }, + * rate_limiter?: bool|array{ // Rate limiter configuration + * enabled?: bool|Param, // Default: false + * limiters?: array, + * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. + * interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". + * interval?: scalar|Param|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * amount?: int|Param, // Amount of tokens to add each interval. // Default: 1 + * }, + * anchor_at?: scalar|Param|null, // Aligns the "fixed_window" policy to a calendar (e.g. "2024-01-05 00:00:00 UTC" combined with `interval: 1 month` resets the counter on the 5th of each month). UTC if not specified. // Default: null + * }>, + * }, + * uid?: bool|array{ // Uid configuration + * enabled?: bool|Param, // Default: false + * default_uuid_version?: 7|6|4|1|Param, // Default: 7 + * name_based_uuid_version?: 5|3|Param, // Default: 5 + * name_based_uuid_namespace?: scalar|Param|null, + * time_based_uuid_version?: 7|6|1|Param, // Default: 7 + * time_based_uuid_node?: scalar|Param|null, + * uuid47_secret?: scalar|Param|null, // A high-entropy secret used by the "uuid47_transformer" service. Defaults to "kernel.secret". // Default: null + * }, + * html_sanitizer?: bool|array{ // HtmlSanitizer configuration + * enabled?: bool|Param, // Default: false + * sanitizers?: array, + * block_elements?: Param|string|list, + * drop_elements?: Param|string|list, + * allow_attributes?: array, + * drop_attributes?: array, + * force_attributes?: array>, + * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false + * allowed_link_schemes?: Param|string|list, + * allowed_link_hosts?: Param|null|string|list, + * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false + * allowed_media_schemes?: Param|string|list, + * allowed_media_hosts?: Param|null|string|list, + * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false + * with_attribute_sanitizers?: Param|string|list, + * without_attribute_sanitizers?: Param|string|list, + * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 + * }>, + * }, + * webhook?: bool|array{ // Webhook configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|Param|null, // The message bus to use. // Default: "messenger.default_bus" + * event_header_name?: scalar|Param|null, // Default: "Webhook-Event" + * id_header_name?: scalar|Param|null, // Default: "Webhook-Id" + * signature_header_name?: scalar|Param|null, // Default: "Webhook-Signature" + * signing_algorithm?: scalar|Param|null, // Default: "sha256" + * routing?: array, + * }, + * remote_event?: bool|array{ // RemoteEvent configuration + * enabled?: bool|Param, // Default: false + * }, + * json_streamer?: bool|array{ // JSON streamer configuration + * enabled?: bool|Param, // Default: false + * default_options?: array{ + * include_null_properties?: bool|Param, // Encode the properties with null value // Default: false + * ... + * }, + * }, + * } + * @psalm-type TwigConfig = array{ + * form_themes?: list, + * globals?: array + * }>, + * autoescape_service?: scalar|Param|null, // Default: null + * autoescape_service_method?: scalar|Param|null, // Default: null + * cache?: scalar|Param|null, // Default: true + * charset?: scalar|Param|null, // Default: "%kernel.charset%" + * debug?: bool|Param, // Default: "%kernel.debug%" + * strict_variables?: bool|Param, // Default: "%kernel.debug%" + * auto_reload?: scalar|Param|null, + * optimizations?: int|Param, + * default_path?: scalar|Param|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" + * file_name_pattern?: Param|string|list, + * paths?: array, + * date?: array{ // The default format options used by the date filter. + * format?: scalar|Param|null, // Default: "F j, Y H:i" + * interval_format?: scalar|Param|null, // Default: "%d days" + * timezone?: scalar|Param|null, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null + * }, + * number_format?: array{ // The default format options for the number_format filter. + * decimals?: int|Param, // Default: 0 + * decimal_point?: scalar|Param|null, // Default: "." + * thousands_separator?: scalar|Param|null, // Default: "," + * }, + * mailer?: array{ + * html_to_text_converter?: scalar|Param|null, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null + * }, + * } + * @psalm-type SecurityConfig = array{ + * access_denied_url?: scalar|Param|null, // Default: null + * session_fixation_strategy?: "none"|"migrate"|"invalidate"|Param, // Default: "migrate" + * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All|Param, // Default: "none" + * erase_credentials?: bool|Param, // Deprecated: Setting the "security.erase_credentials.erase_credentials" configuration option is deprecated. It will be removed in Symfony 9.0, as the "eraseCredentials()" method was removed in Symfony 8.0. // Default: true + * access_decision_manager?: array{ + * strategy?: "affirmative"|"consensus"|"unanimous"|"priority"|Param, + * service?: scalar|Param|null, + * strategy_service?: scalar|Param|null, + * allow_if_all_abstain?: bool|Param, // Default: false + * allow_if_equal_granted_denied?: bool|Param, // Default: true + * }, + * password_hashers?: array, + * hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" + * key_length?: scalar|Param|null, // Default: 40 + * ignore_case?: bool|Param, // Default: false + * encode_as_base64?: bool|Param, // Default: true + * iterations?: scalar|Param|null, // Default: 5000 + * cost?: int|Param, // Default: null + * memory_cost?: scalar|Param|null, // Default: null + * time_cost?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * }>, + * providers?: array, + * }, + * memory?: array{ + * users?: array, + * }>, + * }, + * ldap?: array{ + * service?: scalar|Param|null, + * base_dn?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: null + * search_password?: scalar|Param|null, // Default: null + * extra_fields?: list, + * default_roles?: Param|string|list, + * role_fetcher?: scalar|Param|null, // Default: null + * uid_key?: scalar|Param|null, // Default: "sAMAccountName" + * filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})" + * password_attribute?: scalar|Param|null, // Default: null + * }, + * entity?: array{ + * class?: scalar|Param|null, // The full entity class name of your user class. + * property?: scalar|Param|null, // Default: null + * manager_name?: scalar|Param|null, // Default: null + * }, + * }>, + * firewalls?: array, + * security?: bool|Param, // Default: true + * user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" + * request_matcher?: scalar|Param|null, + * access_denied_url?: scalar|Param|null, + * access_denied_handler?: scalar|Param|null, + * entry_point?: scalar|Param|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". + * provider?: scalar|Param|null, + * stateless?: bool|Param, // Default: false + * lazy?: bool|Param, // Default: false + * context?: scalar|Param|null, + * logout?: array{ + * enable_csrf?: bool|Param|null, // Default: null + * csrf_token_id?: scalar|Param|null, // Default: "logout" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_manager?: scalar|Param|null, + * path?: scalar|Param|null, // Default: "/logout" + * target?: scalar|Param|null, // Default: "/" + * invalidate_session?: bool|Param, // Default: true + * clear_site_data?: Param|string|list<"*"|"cache"|"cookies"|"storage"|"clientHints"|"executionContexts"|"prefetchCache"|"prerenderCache"|Param>, + * delete_cookies?: Param|string|array, + * }, + * switch_user?: array{ + * provider?: scalar|Param|null, + * parameter?: scalar|Param|null, // Default: "_switch_user" + * role?: scalar|Param|null, // Default: "ROLE_ALLOWED_TO_SWITCH" + * target_route?: scalar|Param|null, // Default: null + * }, + * required_badges?: list, + * custom_authenticators?: list, + * login_throttling?: array{ + * limiter?: scalar|Param|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". + * max_attempts?: int|Param, // Default: 5 + * interval?: scalar|Param|null, // Default: "1 minute" + * lock_factory?: scalar|Param|null, // The service ID of the lock factory used by the login rate limiter ("auto" to use the default one when the Lock component is configured, or null to disable locking). // Default: "auto" + * cache_pool?: string|Param, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" + * storage_service?: string|Param, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null + * }, + * x509?: array{ + * provider?: scalar|Param|null, + * user?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN_Email" + * credentials?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN" + * user_identifier?: scalar|Param|null, // Default: "emailAddress" + * }, + * remote_user?: array{ + * provider?: scalar|Param|null, + * user?: scalar|Param|null, // Default: "REMOTE_USER" + * }, + * login_link?: array{ + * check_route?: scalar|Param|null, // Route that will validate the login link - e.g. "app_login_link_verify". + * check_post_only?: scalar|Param|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false + * signature_properties?: list, + * lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600 + * max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null + * used_link_cache?: scalar|Param|null, // Cache service id used to expired links of max_uses is set. + * success_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * failure_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * provider?: scalar|Param|null, // The user provider to load users from. + * secret?: scalar|Param|null, // Default: "%kernel.secret%" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * login_path?: scalar|Param|null, // Default: "/login" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * }, + * form_login?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_parameter?: scalar|Param|null, // Default: "_username" + * password_parameter?: scalar|Param|null, // Default: "_password" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * }, + * form_login_ldap?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_parameter?: scalar|Param|null, // Default: "_username" + * password_parameter?: scalar|Param|null, // Default: "_password" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * json_login?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_path?: scalar|Param|null, // Default: "username" + * password_path?: scalar|Param|null, // Default: "password" + * }, + * json_login_ldap?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_path?: scalar|Param|null, // Default: "username" + * password_path?: scalar|Param|null, // Default: "password" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * access_token?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: null + * token_extractors?: Param|string|list, + * token_handler?: Param|string|array{ + * id?: scalar|Param|null, + * oidc_user_info?: Param|string|array{ + * base_uri?: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). The latter must end with a slash when it has a path, as the discovery path is resolved against it as a relative URL. + * discovery?: array{ // Enable the OIDC discovery. + * cache?: array{ + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" + * client?: scalar|Param|null, // HttpClient service id to use to call the OIDC server. + * }, + * oidc?: array{ + * discovery?: array{ // Enable the OIDC discovery. + * base_uri?: Param|string|list, + * cache?: array{ + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * enforce_key_usage_verification?: bool|Param, // When enabled (default), only keys explicitly designated for signature (via "use":"sig" or a "key_ops" entry containing "sign"/"verify") are accepted. When disabled, keys without any usage designation are also accepted; keys explicitly restricted to encryption are still rejected. // Default: true + * }, + * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" + * audience?: scalar|Param|null, // Audience set in the token, for validation purpose. + * issuers?: list, + * algorithms?: list, + * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). + * encryption?: bool|array{ + * enabled?: bool|Param, // Default: false + * enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false + * algorithms?: list, + * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * }, + * }, + * cas?: array{ + * validation_url?: scalar|Param|null, // CAS server validation URL + * prefix?: scalar|Param|null, // CAS prefix // Default: "cas" + * http_client?: scalar|Param|null, // HTTP Client service // Default: null + * }, + * oauth2?: scalar|Param|null, + * }, + * }, + * http_basic?: array{ + * provider?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: "Secured Area" + * }, + * http_basic_ldap?: array{ + * provider?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: "Secured Area" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * remember_me?: array{ + * secret?: scalar|Param|null, // Default: "%kernel.secret%" + * service?: scalar|Param|null, + * user_providers?: Param|string|list, + * catch_exceptions?: bool|Param, // Default: true + * signature_properties?: list, + * token_provider?: Param|string|array{ + * service?: scalar|Param|null, // The service ID of a custom remember-me token provider. + * doctrine?: bool|array{ + * enabled?: bool|Param, // Default: false + * connection?: scalar|Param|null, // Default: null + * }, + * }, + * token_verifier?: scalar|Param|null, // The service ID of a custom rememberme token verifier. + * name?: scalar|Param|null, // Default: "REMEMBERME" + * lifetime?: int|Param, // Default: 31536000 + * path?: scalar|Param|null, // Default: "/" + * domain?: scalar|Param|null, // Default: null + * secure?: true|false|"auto"|Param, // Defaults to the value of "framework.session.cookie_secure", or to "auto". + * httponly?: bool|Param, // Default: true + * samesite?: null|"lax"|"strict"|"none"|Param, // Defaults to the value of "framework.session.cookie_samesite", or to "lax". + * always_remember_me?: bool|Param, // Default: false + * remember_me_parameter?: scalar|Param|null, // Default: "_remember_me" + * }, + * }>, + * access_control?: list, + * attributes?: array, + * route?: scalar|Param|null, // Default: null + * methods?: Param|string|list, + * allow_if?: scalar|Param|null, // Default: null + * roles?: Param|string|list, + * }>, + * role_hierarchy?: array>, + * } + * @psalm-type TwigComponentConfig = array{ + * defaults?: array, + * anonymous_template_directory?: scalar|Param|null, // Defaults to `components` + * profiler?: bool|array{ // Enables the profiler for Twig Component + * enabled?: bool|Param, // Default: "%kernel.debug%" + * collect_components?: bool|Param, // Collect components instances // Default: true + * }, + * } + * @psalm-type StimulusConfig = array{ + * controller_paths?: list, + * controllers_json?: scalar|Param|null, // Default: "%kernel.project_dir%/assets/controllers.json" + * } + * @psalm-type SyliusTwigHooksConfig = array{ + * enable_autoprefixing?: bool|Param, // Default: false + * hook_name_section_separator?: scalar|Param|null, // Default: false + * supported_hookable_types?: array, + * hooks?: array, + * props?: array, + * configuration?: array, + * priority?: int|Param, // Default: null + * condition?: scalar|Param|null, // Default: null + * ... + * }>>, + * } + * @psalm-type BabdevPagerfantaConfig = array{ + * default_view?: scalar|Param|null, // Default: "default" + * default_sequential_view?: scalar|Param|null, // The view to render pagers which the default view cannot render (i.e. cursor pagers with a numbered view), defaults to the "_sequential" view when one exists // Default: null + * default_twig_template?: scalar|Param|null, // Default: "@BabDevPagerfanta/default.html.twig" + * exceptions_strategy?: array{ + * out_of_range_page?: "to_http_not_found"|"custom"|Param, // Default: "to_http_not_found" + * not_valid_current_page?: "to_http_not_found"|"custom"|Param, // Default: "to_http_not_found" + * invalid_cursor?: "to_http_bad_request"|"custom"|Param, // Default: "to_http_bad_request" + * }, + * } + * @psalm-type SyliusResourceConfig = array{ + * resources?: array, + * settings?: array{ + * paginate?: mixed, // Default: null + * limit?: mixed, // Default: null + * allowed_paginate?: list, + * default_page_size?: int|Param, // Default: 10 + * default_templates_dir?: scalar|Param|null, // Default: null + * sortable?: bool|Param, // Default: false + * sorting?: mixed, // Default: null + * filterable?: bool|Param, // Default: false + * criteria?: mixed, // Default: null + * state_machine_component?: scalar|Param|null, // Default: null + * }, + * translation?: bool|array{ + * enabled?: bool|Param, // Default: true + * locale_provider?: scalar|Param|null, // Default: "sylius.translation_locale_provider.immutable" + * }, + * drivers?: list<"doctrine/orm"|"doctrine/mongodb-odm"|"doctrine/phpcr-odm"|Param>, + * mapping?: array{ + * imports?: list, + * paths?: list, + * }, + * authorization_checker?: scalar|Param|null, // Default: "sylius.resource_controller.authorization_checker.disabled" + * routing_path_bc_layer?: bool|Param, + * path_segment_name_generator?: scalar|Param|null, // Specify a path name generator to use. // Default: "sylius.metadata.path_segment_name_generator.dash" + * } + * @psalm-type KnpMenuConfig = array{ + * providers?: array{ + * builder_alias?: bool|Param, // Default: true + * }, + * twig?: array{ + * template?: scalar|Param|null, // Default: "@KnpMenu/menu.html.twig" + * }, + * route_voter?: bool|Param, // Default: true + * default_renderer?: scalar|Param|null, // Default: "twig" + * } + * @psalm-type SyliusAdminUiConfig = array{ + * routing?: array, + * } + * @psalm-type SyliusGridConfig = array{ + * drivers?: list<"doctrine/orm"|"doctrine/phpcr-odm"|Param>, + * templates?: array{ + * filter?: array, + * action?: array, + * bulk_action?: array, + * }, + * grids?: array, + * }, + * sorting?: array, + * limits?: list, + * fields?: array, + * }>, + * filters?: array, + * form_options?: list, + * default_value?: mixed, + * }>, + * actions?: array, + * }>>, + * removals?: array, + * }>, + * } + * @psalm-type SyliusTwigExtraConfig = array{ + * twig_ux?: array{ + * anonymous_component_template_prefixes?: array, + * }, + * } + * @psalm-type DoctrineConfig = array{ + * dbal?: array{ + * default_connection?: scalar|Param|null, + * types?: array, + * driver_schemes?: array, + * connections?: array, + * mapping_types?: array, + * default_table_options?: array, + * schema_manager_factory?: scalar|Param|null, // Default: "doctrine.dbal.default_schema_manager_factory" + * result_cache?: scalar|Param|null, + * replicas?: array + * }>, + * ... + * }>, + * ... + * }, + * orm?: array{ + * default_entity_manager?: scalar|Param|null, + * enable_native_lazy_objects?: bool|Param, // Deprecated: The "enable_native_lazy_objects" option is deprecated and will be removed in DoctrineBundle 4.0, as native lazy objects are now always enabled. // Default: true + * controller_resolver?: bool|array{ + * enabled?: bool|Param, // Default: true + * auto_mapping?: bool|Param, // Deprecated: The "doctrine.orm.controller_resolver.auto_mapping.auto_mapping" option is deprecated and will be removed in DoctrineBundle 4.0, as it only accepts `false` since 3.0. // Set to true to enable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: false + * evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false + * }, + * entity_managers?: array, + * }>, + * }>, + * ... + * }, + * connection?: scalar|Param|null, + * class_metadata_factory_name?: scalar|Param|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" + * default_repository_class?: scalar|Param|null, // Default: "Doctrine\\ORM\\EntityRepository" + * auto_mapping?: scalar|Param|null, // Default: false + * naming_strategy?: scalar|Param|null, // Default: "doctrine.orm.naming_strategy.default" + * quote_strategy?: scalar|Param|null, // Default: "doctrine.orm.quote_strategy.default" + * typed_field_mapper?: scalar|Param|null, // Default: "doctrine.orm.typed_field_mapper.default" + * entity_listener_resolver?: scalar|Param|null, // Default: null + * fetch_mode_subselect_batch_size?: scalar|Param|null, + * repository_factory?: scalar|Param|null, // Default: "doctrine.orm.container_repository_factory" + * schema_ignore_classes?: list, + * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728. // Default: false + * second_level_cache?: array{ + * region_cache_driver?: Param|string|array{ + * type?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * pool?: scalar|Param|null, + * }, + * region_lock_lifetime?: scalar|Param|null, // Default: 60 + * log_enabled?: bool|Param, // Default: true + * region_lifetime?: scalar|Param|null, // Default: 3600 + * enabled?: bool|Param, // Default: true + * factory?: scalar|Param|null, + * regions?: array, + * loggers?: array, + * }, + * hydrators?: array, + * mappings?: array, + * dql?: array{ + * string_functions?: array, + * numeric_functions?: array, + * datetime_functions?: array, + * }, + * filters?: array, + * ... + * }>, + * identity_generation_preferences?: array, + * }>, + * resolve_target_entities?: array, + * ... + * }, + * } + * @psalm-type TwigExtraConfig = array{ + * cache?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * html?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * markdown?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * intl?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * cssinliner?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * inky?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * string?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * commonmark?: array{ + * renderer?: array{ // Array of options for rendering HTML. + * block_separator?: scalar|Param|null, + * inner_separator?: scalar|Param|null, + * soft_break?: scalar|Param|null, + * }, + * html_input?: "strip"|"allow"|"escape"|Param, // How to handle HTML input. + * allow_unsafe_links?: bool|Param, // Remove risky link and image URLs by setting this to false. // Default: true + * max_nesting_level?: int|Param, // The maximum nesting level for blocks. // Default: 9223372036854775807 + * max_delimiters_per_line?: int|Param, // The maximum number of strong/emphasis delimiters per line. // Default: 9223372036854775807 + * slug_normalizer?: array{ // Array of options for configuring how URL-safe slugs are created. + * instance?: mixed, + * max_length?: int|Param, // Default: 255 + * unique?: mixed, + * }, + * commonmark?: array{ // Array of options for configuring the CommonMark core extension. + * enable_em?: bool|Param, // Default: true + * enable_strong?: bool|Param, // Default: true + * use_asterisk?: bool|Param, // Default: true + * use_underscore?: bool|Param, // Default: true + * unordered_list_markers?: list, + * }, + * ... + * }, + * } + * @psalm-type UxIconsConfig = array{ + * icon_dir?: scalar|Param|null, // The local directory where icons are stored. // Default: "%kernel.project_dir%/assets/icons" + * default_icon_attributes?: array, + * icon_sets?: array, + * suffixes?: array, + * }>, + * }>, + * aliases?: array, + * iconify?: bool|array{ // Configuration for the remote icon service. + * enabled?: bool|Param, // Default: true + * on_demand?: bool|Param, // Whether to download icons "on demand". // Default: true + * auto_lock?: bool|Param, // Persist "on demand" icons to the local icon directory (see "icon_dir"). Recommended in dev only. Requires "on_demand" to be enabled. // Default: false + * endpoint?: scalar|Param|null, // The endpoint for the Iconify icons API. // Default: "https://api.iconify.design" + * }, + * ignore_not_found?: bool|Param, // Ignore error when an icon is not found. Set to 'true' to fail silently. // Default: false + * } + * @psalm-type SymfonycastsTailwindConfig = array{ + * input_css?: list, + * config_file?: scalar|Param|null, // Path to the tailwind.config.js file // Default: "%kernel.project_dir%/tailwind.config.js" + * binary?: scalar|Param|null, // The tailwind binary to use instead of downloading a new one // Default: null + * binary_version?: scalar|Param|null, // Tailwind CLI version to download - required unless "binary" is set (run "tailwind:init" to configure) // Default: null + * binary_platform?: "auto"|"linux-arm64"|"linux-arm64-musl"|"linux-x64"|"linux-x64-musl"|"macos-arm64"|"macos-x64"|"windows-x64"|Param, // Tailwind CLI platform to download - "auto" will try to detect the platform automatically // Default: "auto" + * postcss_config_file?: scalar|Param|null, // Path to PostCSS config file which is passed to the Tailwind CLI // Default: null + * strict_mode?: bool|Param|null, // When enabled, an exception will be thrown if there are no built assets (default: false in `test` env, true otherwise) // Default: null + * process_timeout?: int|Param, // Timeout in seconds for the Tailwind build process - use "0" to disable // Default: 60 + * } + * @psalm-type TalesFromADevTwigExtraTailwindConfig = array{ + * tailwind_merge?: array{ + * additional_configuration?: mixed, // Default: [] + * }, + * } + * @psalm-type SyliusShadcnAdminUiConfig = array{ + * sidebar?: array{ + * variant?: "sidebar"|"floating"|"inset"|Param, // Default variant of the sidebar on every admin page, overridable per page hook. // Default: "sidebar" + * }, + * } + * @psalm-type WhiteOctoberPagerfantaConfig = array{ // Deprecated: The "white_october_pagerfanta" configuration node is deprecated, migrate your configuration to the "babdev_pagerfanta" configuration node. + * exceptions_strategy?: array{ + * out_of_range_page?: scalar|Param|null, // Default: "to_http_not_found" + * not_valid_current_page?: scalar|Param|null, // Default: "to_http_not_found" + * }, + * default_view?: scalar|Param|null, // Default: "default" + * } + * @psalm-type ConfigType = array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * stimulus?: StimulusConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * knp_menu?: KnpMenuConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * doctrine?: DoctrineConfig, + * twig_extra?: TwigExtraConfig, + * ux_icons?: UxIconsConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, + * tales_from_a_dev_twig_extra_tailwind?: TalesFromADevTwigExtraTailwindConfig, + * sylius_shadcn_admin_ui?: SyliusShadcnAdminUiConfig, + * white_october_pagerfanta?: WhiteOctoberPagerfantaConfig, + * "when@dev"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * stimulus?: StimulusConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * knp_menu?: KnpMenuConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * doctrine?: DoctrineConfig, + * twig_extra?: TwigExtraConfig, + * ux_icons?: UxIconsConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, + * tales_from_a_dev_twig_extra_tailwind?: TalesFromADevTwigExtraTailwindConfig, + * sylius_shadcn_admin_ui?: SyliusShadcnAdminUiConfig, + * }, + * "when@prod"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * stimulus?: StimulusConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * knp_menu?: KnpMenuConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * doctrine?: DoctrineConfig, + * twig_extra?: TwigExtraConfig, + * ux_icons?: UxIconsConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, + * tales_from_a_dev_twig_extra_tailwind?: TalesFromADevTwigExtraTailwindConfig, + * sylius_shadcn_admin_ui?: SyliusShadcnAdminUiConfig, + * }, + * "when@test"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * stimulus?: StimulusConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * knp_menu?: KnpMenuConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * doctrine?: DoctrineConfig, + * twig_extra?: TwigExtraConfig, + * ux_icons?: UxIconsConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, + * tales_from_a_dev_twig_extra_tailwind?: TalesFromADevTwigExtraTailwindConfig, + * sylius_shadcn_admin_ui?: SyliusShadcnAdminUiConfig, + * }, + * ..., + * }> + * } + */ +final class App +{ + /** + * @param ConfigType $config + * + * @psalm-return ConfigType + */ + public static function config(array $config): array + { + /** @var ConfigType $config */ + $config = AppReference::config($config); + + return $config; + } +} + +namespace Symfony\Component\Routing\Loader\Configurator; + +/** + * This class provides array-shapes for configuring the routes of an application. + * + * Example: + * + * ```php + * // config/routes.php + * namespace Symfony\Component\Routing\Loader\Configurator; + * + * return Routes::config([ + * 'controllers' => [ + * 'resource' => 'routing.controllers', + * ], + * ]); + * ``` + * + * @psalm-type RouteConfig = array{ + * path: string|array, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type ImportConfig = array{ + * resource: string, + * type?: string, + * exclude?: string|list, + * prefix?: string|array, + * name_prefix?: string, + * trailing_slash_on_root?: bool, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type AliasConfig = array{ + * alias: string, + * deprecated?: array{package:string, version:string, message?:string}, + * } + * @psalm-type RoutesConfig = array{ + * "when@dev"?: array, + * "when@prod"?: array, + * "when@test"?: array, + * ... + * } + */ +final class Routes +{ + /** + * @param RoutesConfig $config + * + * @psalm-return RoutesConfig + */ + public static function config(array $config): array + { + return $config; + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/routes.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/routes.yaml new file mode 100644 index 00000000..c779b3eb --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/routes.yaml @@ -0,0 +1,7 @@ +# The demo has no front office: the root redirects to the admin panel. +home: + path: / + controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController + defaults: + route: sylius_admin_ui_dashboard + permanent: false diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/routes/security.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/routes/security.yaml new file mode 100644 index 00000000..f853be15 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/routes/security.yaml @@ -0,0 +1,3 @@ +_security_logout: + resource: security.route_loader.logout + type: service diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_admin_ui.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_admin_ui.yaml new file mode 100644 index 00000000..23595fff --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_admin_ui.yaml @@ -0,0 +1,3 @@ +sylius_admin_ui: + resource: '@SyliusAdminUiBundle/config/routes.php' + prefix: /admin diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml new file mode 100644 index 00000000..6744c22b --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml @@ -0,0 +1,3 @@ +sylius_resource_loader: + resource: 'sylius.symfony.routing.loader.resource' + type: service diff --git a/src/ShadcnAdminUi/tests/Functional/.application/config/services.yaml b/src/ShadcnAdminUi/tests/Functional/.application/config/services.yaml new file mode 100644 index 00000000..75195946 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/config/services.yaml @@ -0,0 +1,14 @@ +parameters: + locale: 'en' + +services: + _defaults: + autowire: true + autoconfigure: true + + TestApplication\Sylius\ShadcnAdminUi\: + resource: '../src/' + exclude: + - '../src/Entity/' + - '../src/Enum/' + - '../src/Kernel.php' diff --git a/src/ShadcnAdminUi/tests/Functional/.application/importmap.php b/src/ShadcnAdminUi/tests/Functional/.application/importmap.php new file mode 100644 index 00000000..c4039241 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/importmap.php @@ -0,0 +1,21 @@ + ['path' => './assets/admin.js', 'entrypoint' => true], + '@hotwired/stimulus' => ['version' => '3.2.2'], + '@symfony/stimulus-bundle' => ['path' => '../../../vendor/symfony/stimulus-bundle/assets/dist/loader.js'], +]; diff --git a/src/ShadcnAdminUi/tests/Functional/.application/public/index.php b/src/ShadcnAdminUi/tests/Functional/.application/public/index.php new file mode 100644 index 00000000..20222ed9 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/public/index.php @@ -0,0 +1,28 @@ + dirname(__DIR__), +]; + +if (file_exists(dirname(__DIR__, 4) . '/vendor/autoload_runtime.php')) { + require_once dirname(__DIR__, 4) . '/vendor/autoload_runtime.php'; +} else { + require_once dirname(__DIR__, 6) . '/vendor/autoload_runtime.php'; +} + +return function (array $context) { + return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); +}; diff --git a/src/ShadcnAdminUi/tests/Functional/.application/src/Command/LoadBooksCommand.php b/src/ShadcnAdminUi/tests/Functional/.application/src/Command/LoadBooksCommand.php new file mode 100644 index 00000000..8032356b --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/src/Command/LoadBooksCommand.php @@ -0,0 +1,86 @@ +addOption('if-empty', null, InputOption::VALUE_NONE, 'Only loads the data when the table is empty'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $repository = $this->em->getRepository(Book::class); + + if ($input->getOption('if-empty') && $repository->count([]) > 0) { + $io->note('Books already exist: nothing to do.'); + + return Command::SUCCESS; + } + + $this->em->createQuery('DELETE FROM ' . Book::class)->execute(); + + foreach (self::BOOKS as $i => [$title, $author, $category, $available, $summary]) { + $book = new Book(); + $book->setTitle($title); + $book->setAuthorName($author); + $book->setCategory($category); + $book->setAvailable($available); + $book->setSummary($summary); + $book->setCreatedAt(new \DateTimeImmutable(sprintf('-%d days', $i * 3))); + $this->em->persist($book); + } + $this->em->flush(); + + $io->success(sprintf('%d books loaded.', \count(self::BOOKS))); + + return Command::SUCCESS; + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/src/Command/UpdateShadcnRecipesCommand.php b/src/ShadcnAdminUi/tests/Functional/.application/src/Command/UpdateShadcnRecipesCommand.php new file mode 100644 index 00000000..1800acde --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/src/Command/UpdateShadcnRecipesCommand.php @@ -0,0 +1,192 @@ + → , component('Button') → component('shadcn:Button'); + * - Stimulus controllers: "dialog" → "shadcn--dialog" (data-controller, actions, targets, values...). + */ +#[AsCommand('app:shadcn:update', 'Updates the shadcn kit recipes bundled in templates/components/shadcn and assets/controllers/shadcn')] +final class UpdateShadcnRecipesCommand extends Command +{ + private const PREFIX = 'shadcn'; + + protected function configure(): void + { + $this->addArgument('recipes', InputArgument::IS_ARRAY, 'Recipes to update (e.g. "button dialog"), every bundled recipe by default'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $recipes = $input->getArgument('recipes'); + + if (!$this->getApplication()?->has('ux:install')) { + $io->error('The "ux:install" command is missing: run this command in the "dev" environment, with symfony/ux-toolkit installed.'); + + return Command::FAILURE; + } + + $bundleDir = \dirname(__DIR__, 5); + $componentsDir = $bundleDir . '/templates/components/' . self::PREFIX; + $controllersDir = $bundleDir . '/assets/controllers/' . self::PREFIX; + + $recipes = $recipes ?: $this->bundledRecipes($componentsDir, $bundleDir . '/vendor/symfony/ux-toolkit/kits/shadcn'); + $filesystem = new Filesystem(); + $tmpDir = $filesystem->tempnam(sys_get_temp_dir(), 'shadcn-recipes-'); + $filesystem->remove($tmpDir); + + try { + foreach ($recipes as $recipe) { + $output = new BufferedOutput(); + $status = $this->getApplication()->find('ux:install')->run(new ArrayInput([ + 'recipe' => $recipe, + '--kit' => 'shadcn', + '--destination' => $tmpDir, + '--force' => true, + ]), $output); + + if (Command::SUCCESS !== $status) { + $io->error(\sprintf('Unable to install the "%s" recipe:', $recipe)); + $io->writeln($output->fetch()); + + return Command::FAILURE; + } + } + + $controllers = $this->controllerIdentifiers($tmpDir . '/assets/controllers'); + $files = [ + ...$this->copy($tmpDir . '/templates/components', $componentsDir, $controllers, $filesystem), + ...$this->copy($tmpDir . '/assets/controllers', $controllersDir, $controllers, $filesystem), + ]; + } finally { + $filesystem->remove($tmpDir); + } + + $io->listing(array_map(static fn (string $file): string => Path::makeRelative($file, $bundleDir), $files)); + $io->success(\sprintf('%d recipe(s) updated. Review the git diff: local changes to the recipes must be restored by hand.', \count($recipes))); + + return Command::SUCCESS; + } + + /** + * Recipes whose main component is bundled: "AlertDialog.html.twig" → "alert-dialog". + * + * @return list + */ + private function bundledRecipes(string $componentsDir, string $kitDir): array + { + $recipes = []; + + foreach ((new Finder())->files()->in($componentsDir)->depth(0)->name('*.html.twig')->sortByName() as $file) { + $recipe = strtolower((string) preg_replace('/(?getBasename('.html.twig'))); + + if (is_dir($kitDir . '/' . $recipe)) { + $recipes[] = $recipe; + } + } + + return $recipes; + } + + /** + * Stimulus identifiers of the installed controllers, longest first ("dropdown-menu-sub" before "dropdown-menu"). + * + * @return list + */ + private function controllerIdentifiers(string $controllersDir): array + { + if (!is_dir($controllersDir)) { + return []; + } + + $identifiers = []; + + foreach ((new Finder())->files()->in($controllersDir)->name('*_controller.js') as $file) { + $identifiers[] = str_replace('_', '-', $file->getBasename('_controller.js')); + } + + usort($identifiers, static fn (string $a, string $b): int => \strlen($b) <=> \strlen($a)); + + return $identifiers; + } + + /** + * @param list $controllers + * + * @return list Written files + */ + private function copy(string $sourceDir, string $targetDir, array $controllers, Filesystem $filesystem): array + { + if (!is_dir($sourceDir)) { + return []; + } + + $written = []; + + foreach ((new Finder())->files()->in($sourceDir)->sortByName() as $file) { + $target = $targetDir . '/' . $file->getRelativePathname(); + $filesystem->dumpFile($target, $this->rewrite($file->getContents(), $controllers)); + $written[] = $target; + } + + return $written; + } + + /** + * @param list $controllers + */ + private function rewrite(string $content, array $controllers): string + { + $content = (string) preg_replace('/<(\/?)twig:(?=[A-Z])/', '<$1twig:' . self::PREFIX . ':', $content); + $content = (string) preg_replace("/\\bcomponent\\((['\"])(?=[A-Z])/", 'component($1' . self::PREFIX . ':', $content); + + foreach ($controllers as $controller) { + $id = preg_quote($controller, '/'); + $prefixed = self::PREFIX . '--' . $controller; + + // Actions ("click->dialog#open", "dialog#open") and Stimulus attributes ("data-dialog-target", "data-dialog-open-value"), + // but not plain data attributes such as "data-collapsible-mode". + $content = (string) preg_replace('/(? $match[1] . preg_replace('/(?createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(?string $title): void + { + $this->title = $title; + } + + public function getAuthorName(): ?string + { + return $this->authorName; + } + + public function setAuthorName(?string $authorName): void + { + $this->authorName = $authorName; + } + + public function getCategory(): ?BookCategory + { + return $this->category; + } + + public function setCategory(?BookCategory $category): void + { + $this->category = $category; + } + + public function isAvailable(): bool + { + return $this->available; + } + + public function setAvailable(bool $available): void + { + $this->available = $available; + } + + public function getSummary(): ?string + { + return $this->summary; + } + + public function setSummary(?string $summary): void + { + $this->summary = $summary; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): void + { + $this->createdAt = $createdAt; + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/src/Enum/BookCategory.php b/src/ShadcnAdminUi/tests/Functional/.application/src/Enum/BookCategory.php new file mode 100644 index 00000000..abd86083 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/src/Enum/BookCategory.php @@ -0,0 +1,36 @@ + 'blue', + self::MYSTERY => 'green', + self::SCIENCE_FICTION => 'purple', + self::FANTASY => 'orange', + self::HISTORY => 'yellow', + self::TECHNOLOGY => 'red', + }; + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/src/Form/BookType.php b/src/ShadcnAdminUi/tests/Functional/.application/src/Form/BookType.php new file mode 100644 index 00000000..a492d99b --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/src/Form/BookType.php @@ -0,0 +1,53 @@ +add('title', null, ['label' => 'app.ui.title', 'help' => 'app.ui.title_help']) + ->add('authorName', null, ['label' => 'app.ui.author_name']) + ->add('category', EnumType::class, [ + 'label' => 'app.ui.category', + 'class' => BookCategory::class, + 'choice_label' => fn (BookCategory $choice) => ucfirst($choice->value), + 'required' => false, + 'placeholder' => 'app.ui.choose_category', + ]) + ->add('summary', TextareaType::class, ['label' => 'app.ui.summary', 'required' => false]) + ->add('available', CheckboxType::class, ['label' => 'app.ui.available', 'required' => false]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults(['data_class' => Book::class]); + } + + public function getBlockPrefix(): string + { + return 'sylius_resource'; + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/src/Grid/BookGrid.php b/src/ShadcnAdminUi/tests/Functional/.application/src/Grid/BookGrid.php new file mode 100644 index 00000000..9cdaff54 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/src/Grid/BookGrid.php @@ -0,0 +1,81 @@ +value)] = $category->value; + } + + $gridBuilder + ->orderBy('title') + ->setLimits([10, 25, 50]) + ->addFilter(StringFilter::create('search', ['title', 'authorName'])->setLabel('sylius.ui.search')) + ->addFilter(SelectFilter::create('category', $categories)->setLabel('app.ui.category')) + ->addFilter(BooleanFilter::create('available')->setLabel('app.ui.available')) + ->addFilter(DateFilter::create('createdAt')->setLabel('app.ui.created_at')) + ->addField(StringField::create('title')->setLabel('app.ui.title')->setSortable(true)) + ->addField(StringField::create('authorName')->setLabel('app.ui.author_name')->setSortable(true)) + ->addField(TwigField::create('category', 'book/grid/category.html.twig')->setLabel('app.ui.category')) + ->addField( + TwigField::create('available', '@SyliusShadcnAdminUi/shared/grid/field/boolean.html.twig') + ->setLabel('app.ui.available') + ->withOptions([ + 'vars' => [ + 'th_class' => 'text-center', + 'td_class' => 'text-center', + ], + ]), + ) + ->addField(DateTimeField::create('createdAt', 'd/m/Y')->setLabel('app.ui.created_at')->setSortable(true)) + ->addActionGroup(MainActionGroup::create(CreateAction::create())) + ->addActionGroup(ItemActionGroup::create(ShowAction::create(), UpdateAction::create(), DeleteAction::create())) + ->addActionGroup(BulkActionGroup::create(DeleteAction::create())) + ; + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/src/Kernel.php b/src/ShadcnAdminUi/tests/Functional/.application/src/Kernel.php new file mode 100644 index 00000000..679169d9 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/src/Kernel.php @@ -0,0 +1,27 @@ +menuBuilder->createMenu($options); + + $menu->addChild('dashboard', ['route' => 'sylius_admin_ui_dashboard']) + ->setLabel('sylius.ui.dashboard') + ->setLabelAttribute('icon', 'lucide:layout-dashboard') + ; + + $library = $menu->addChild('library') + ->setLabel('app.ui.library') + ->setLabelAttribute('icon', 'lucide:library') + ; + $library->addChild('books', ['route' => 'app_admin_book_index'])->setLabel('app.ui.books'); + $library->addChild('new_book', ['route' => 'app_admin_book_create'])->setLabel('app.ui.new_book'); + + $menu->addChild('section', ['extras' => ['section' => true]]) + ->setLabel('app.ui.section') + ; + $subMenu = $menu->addChild('submenu') + ->setLabel('app.ui.submenu') + ->setLabelAttribute('icon', 'lucide:menu') + ; + $subMenu->addChild('section_1', ['extras' => ['header' => true]])->setLabel('app.ui.section_1'); + $subMenu->addChild('item_1', ['route' => 'app_admin_book_index'])->setLabel('app.ui.item_1'); + + $menu->addChild('docs', ['uri' => 'https://stack.sylius.com', 'linkAttributes' => ['target' => '_blank']]) + ->setLabel('sylius.ui.documentation') + ->setLabelAttribute('icon', 'lucide:book-open') + ; + + return $menu; + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/src/Twig/Components/DashboardStats.php b/src/ShadcnAdminUi/tests/Functional/.application/src/Twig/Components/DashboardStats.php new file mode 100644 index 00000000..eccba567 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/src/Twig/Components/DashboardStats.php @@ -0,0 +1,64 @@ +em->getRepository(Book::class)->count([]); + } + + public function getAvailable(): int + { + return $this->em->getRepository(Book::class)->count(['available' => true]); + } + + /** + * @return list + */ + public function getByCategory(): array + { + return $this->em->createQueryBuilder() + ->select('b.category AS category, COUNT(b.id) AS total') + ->from(Book::class, 'b') + ->groupBy('b.category') + ->orderBy('total', 'DESC') + ->getQuery() + ->getResult() + ; + } + + /** + * @return list + */ + public function getLatest(): array + { + return $this->em->getRepository(Book::class)->findBy([], ['createdAt' => 'DESC'], 5); + } +} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/templates/admin/book/show/details.html.twig b/src/ShadcnAdminUi/tests/Functional/.application/templates/admin/book/show/details.html.twig new file mode 100644 index 00000000..90e46b33 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/templates/admin/book/show/details.html.twig @@ -0,0 +1,48 @@ +{% set book = hookable_metadata.context.resource %} + +
    + + + {{ book.title }} + {{ book.authorName }} + + + + {{ 'sylius.ui.edit'|trans }} + + + + +

    + {{ book.summary ?: 'Aucun résumé.' }} +

    +
    +
    + + + + {{ 'sylius.ui.details'|trans }} + + +
    +
    {{ 'app.ui.category'|trans }}
    +
    {% if book.category %}{{ book.category.value|capitalize }}{% else %}—{% endif %}
    + +
    {{ 'app.ui.available'|trans }}
    +
    + {% if book.available %} + {{ 'sylius.ui.yes_label'|trans }} + {% else %} + {{ 'sylius.ui.no_label'|trans }} + {% endif %} +
    + +
    {{ 'app.ui.created_at'|trans }}
    +
    {{ book.createdAt|date('d/m/Y') }}
    + +
    ID
    +
    +
    +
    +
    +
    diff --git a/src/ShadcnAdminUi/tests/Functional/.application/templates/admin/dashboard/stats.html.twig b/src/ShadcnAdminUi/tests/Functional/.application/templates/admin/dashboard/stats.html.twig new file mode 100644 index 00000000..fdfdc480 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/templates/admin/dashboard/stats.html.twig @@ -0,0 +1 @@ + diff --git a/src/ShadcnAdminUi/tests/Functional/.application/templates/book/grid/category.html.twig b/src/ShadcnAdminUi/tests/Functional/.application/templates/book/grid/category.html.twig new file mode 100644 index 00000000..b402af1f --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/templates/book/grid/category.html.twig @@ -0,0 +1 @@ +{% if data %}{{ data.value|capitalize }}{% endif %} diff --git a/src/ShadcnAdminUi/tests/Functional/.application/templates/components/DashboardStats.html.twig b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/DashboardStats.html.twig new file mode 100644 index 00000000..02b520b0 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/DashboardStats.html.twig @@ -0,0 +1,88 @@ +{# Data is read here: inside a nested component, "this" is the child component. #} +{% set total = this.total %} +{% set available = this.available %} +{% set by_category = this.byCategory %} +{% set latest = this.latest %} +{# Full class names: Tailwind does not detect dynamically built classes. #} +{% set bar_colors = ['bg-chart-1', 'bg-chart-2', 'bg-chart-3', 'bg-chart-4', 'bg-chart-5'] %} + +
    +
    + {% for stat in [ + {label: 'app.ui.total_books', value: total, icon: 'lucide:book-copy'}, + {label: 'app.ui.available_books', value: available, icon: 'lucide:circle-check', class: 'text-success'}, + {label: 'app.ui.unavailable_books', value: total - available, icon: 'lucide:circle-x', class: 'text-muted-foreground'}, + {label: 'app.ui.categories', value: by_category|length, icon: 'lucide:tags'}, + ] %} + + + {{ stat.label|trans }} + {{ stat.value }} + + + + + + {% endfor %} +
    + +
    + + + {{ 'app.ui.latest_books'|trans }} + {{ 'app.ui.latest_books_description'|trans }} + + + {{ 'app.ui.see_all'|trans }} + + + + + + + + + {{ 'app.ui.title'|trans }} + {{ 'app.ui.category'|trans }} + {{ 'app.ui.created_at'|trans }} + + + + {% for book in latest %} + + + {{ book.title }} +
    {{ book.authorName }}
    +
    + + {% if book.category %}{{ book.category.value|capitalize }}{% endif %} + + {{ book.createdAt|date('d/m/Y') }} +
    + {% endfor %} +
    +
    +
    +
    + + + + {{ 'app.ui.by_category'|trans }} + + + {% for row in by_category %} + {% set percent = total > 0 ? (row.total / total * 100)|round : 0 %} +
    +
    + {{ row.category ? row.category.value|capitalize : '—' }} + {{ row.total }} +
    +
    +
    +
    +
    + {% endfor %} +
    +
    +
    +
    diff --git a/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ActionButton.html.twig b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ActionButton.html.twig new file mode 100644 index 00000000..9734fdb2 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ActionButton.html.twig @@ -0,0 +1 @@ + diff --git a/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ArrayClasses.html.twig b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ArrayClasses.html.twig new file mode 100644 index 00000000..5f8f763a --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ArrayClasses.html.twig @@ -0,0 +1 @@ +
    diff --git a/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ForwardingCard.html.twig b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ForwardingCard.html.twig new file mode 100644 index 00000000..e132d5b8 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/templates/components/Fixture/ForwardingCard.html.twig @@ -0,0 +1 @@ +Title diff --git a/src/ShadcnAdminUi/tests/Functional/.application/translations/messages.en.yaml b/src/ShadcnAdminUi/tests/Functional/.application/translations/messages.en.yaml new file mode 100644 index 00000000..bdbd3cd8 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/translations/messages.en.yaml @@ -0,0 +1,27 @@ +app: + ui: + book: Book + books: Books + new_book: New book + library: Library + title: Title + title_help: The title as printed on the cover. + author_name: Author + category: Category + choose_category: Choose a category + summary: Summary + available: Available + created_at: Created at + dashboard_subtitle: Library overview + total_books: Books in catalog + available_books: Available + unavailable_books: Unavailable + categories: Categories + latest_books: Latest additions + latest_books_description: The 5 most recently added books + see_all: See all + by_category: Books by category + section: Section + submenu: Submenu + section_1: Section 1 + item_1: Item 1 diff --git a/src/ShadcnAdminUi/tests/Functional/.application/translations/messages.fr.yaml b/src/ShadcnAdminUi/tests/Functional/.application/translations/messages.fr.yaml new file mode 100644 index 00000000..5c4d9ee3 --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/.application/translations/messages.fr.yaml @@ -0,0 +1,27 @@ +app: + ui: + book: Livre + books: Livres + new_book: Nouveau livre + library: Bibliothèque + title: Titre + title_help: Le titre tel qu'imprimé sur la couverture. + author_name: Auteur + category: Catégorie + choose_category: Choisir une catégorie + summary: Résumé + available: Disponible + created_at: Ajouté le + dashboard_subtitle: Vue d'ensemble de la bibliothèque + total_books: Livres au catalogue + available_books: Disponibles + unavailable_books: Indisponibles + categories: Catégories + latest_books: Derniers ajouts + latest_books_description: Les 5 livres ajoutés le plus récemment + see_all: Tout voir + by_category: Répartition par catégorie + section: Section + submenu: Sous-menu + section_1: Section 1 + item_1: Élément 1 diff --git a/src/ShadcnAdminUi/tests/Functional/DemoApplicationTest.php b/src/ShadcnAdminUi/tests/Functional/DemoApplicationTest.php new file mode 100644 index 00000000..cedfccfb --- /dev/null +++ b/src/ShadcnAdminUi/tests/Functional/DemoApplicationTest.php @@ -0,0 +1,127 @@ +client = self::createClient(); + + $application = new Application(self::$kernel); + $application->setAutoExit(false); + + foreach ([['doctrine:schema:drop', '--force' => true], ['doctrine:schema:create'], ['app:load-books']] as $command) { + $output = new BufferedOutput(); + self::assertSame(0, $application->run(new ArrayInput(['command' => array_shift($command)] + $command), $output), $output->fetch()); + } + } + + public function testTheLoginPageRenders(): void + { + $this->client->request('GET', '/admin/login'); + + self::assertResponseIsSuccessful(); + self::assertSelectorExists('input[name="_csrf_admin_security_token"]'); + self::assertStringContainsString('