diff --git a/.agents/skills/comfony-crud/SKILL.md b/.agents/skills/comfony-crud/SKILL.md new file mode 100644 index 0000000..0415095 --- /dev/null +++ b/.agents/skills/comfony-crud/SKILL.md @@ -0,0 +1,563 @@ +--- +name: comfony-crud +description: Comprehensive guide and templates for creating Entities, CRUD operations, DatatableComponent, LiveForms, and Turbo Streams in Comfony. Use this skill whenever implementing or modifying entities, admin pages, search tables, or reactive forms. +metadata: + version: 1.0.0 +--- + +# Comfony CRUD & Component Development Guide + +Comfony is built on Symfony 7.3, PHP 8.4, Hotwire Turbo, Stimulus, UX Live Components, and Mercure. + +--- + +## 1. Entity Definition + +### Standards +- **PHP 8.4** coding standard. +- **Setters MUST accept nullable parameters** (e.g. `public function setTitle(?string $title): static`) — UX LiveComponent requires this to bind form state correctly. +- **Mercure Real-Time Broadcast:** Add `#[Broadcast(topics: ['entity_plural'], private: true)]` to stream entity changes via Mercure. +- **File Attachments:** Use `App\Entity\File\File`. Relations should be `ManyToMany` (or `ManyToOne`). Never modify `File` entity unless strictly required. +- **Traits:** Use Gedmo `TimestampableEntity` and `BlameableEntity`. +- **Slugs:** Use `#[Slug(fields: ['title'])]` from Gedmo. +- **NO Manual Migrations:** Do NOT create migration files (`make:migration`). Comfony handles database schema automatically. Offer to run `./bin/docker-console.sh config:import` (or `./bin/console config:import`). + +### Entity Example +```php +attachments = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(?string $title): static + { + $this->title = $title; + return $this; + } + + public function getBody(): ?string + { + return $this->body; + } + + public function setBody(?string $body): static + { + $this->body = $body; + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(?string $slug): static + { + $this->slug = $slug; + return $this; + } + + /** + * @return Collection + */ + public function getAttachments(): Collection + { + return $this->attachments; + } + + public function addAttachment(File $attachment): static + { + if (!$this->attachments->contains($attachment)) { + $this->attachments->add($attachment); + } + return $this; + } + + public function removeAttachment(File $attachment): static + { + $this->attachments->removeElement($attachment); + return $this; + } + + public function isPublished(): ?bool + { + return $this->published; + } + + public function setPublished(?bool $published): static + { + $this->published = $published; + return $this; + } +} +``` + +--- + +## 2. Admin Controller + +Place controllers under `src/Controller/Admin/{Feature}Controller.php`. + +```php +render('admin/page/index.html.twig', [ + 'title' => $this->translator->trans('Pages'), + ]); + } + + #[Route('/new', name: 'app_admin_page_new', methods: ['GET'])] + public function new(): Response + { + $page = new Page(); + return $this->render('admin/page/edit.html.twig', [ + 'title' => $this->translator->trans('Add New Page'), + 'page' => $page, + ]); + } + + #[Route('/{id}/edit', name: 'app_admin_page_edit', methods: ['GET'])] + public function edit(Page $page): Response + { + return $this->render('admin/page/edit.html.twig', [ + 'title' => $this->translator->trans('Edit Page {title}', ['title' => $page->getTitle()]), + 'page' => $page, + ]); + } + + #[Route('/{id}', name: 'app_admin_page_delete', methods: ['POST'])] + public function delete(Request $request, Page $page, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete' . $page->getId(), $request->request->get('_token'))) { + $entityManager->remove($page); + $entityManager->flush(); + $this->addFlash('success', $this->translator->trans('Page deleted successfully.')); + } + + return $this->redirectToRoute('app_admin_page_index', [], Response::HTTP_SEE_OTHER); + } +} +``` + +--- + +## 3. Search / Datatable Component + +- Location: `src/Twig/Components/{Feature}SearchComponent.php`. +- Must extend `DatatableComponent`. +- Must be annotated with `#[AsLiveComponent(template: '@base_theme/partials/_datatable.html.twig')]`. +- Define `$sort`, `$direction`, `$rowTemplateFile`. +- Implement `getQueryBuilder()` and `getTableBuildData()`. Include at least one column in `quick_filters`. + +```php +entityManager->getRepository(Page::class)->createQueryBuilder('p'); + } + + #[Override] + public function getTableBuildData(): array + { + return [ + 'headers' => [ + ['label' => $this->translator->trans("Actions")], + 'p.id' => ['label' => $this->translator->trans("Id"), 'sortable' => true], + 'p.title' => ['label' => $this->translator->trans("Title"), 'sortable' => true], + ['label' => $this->translator->trans('Published')], + 'p.createdAt' => ['label' => $this->translator->trans('created_at'), 'sortable' => true], + 'p.updatedAt' => ['label' => $this->translator->trans('updated_at'), 'sortable' => true], + ], + "quick_filters" => [ + "p.title" => [] + ], + "filters" => [ + 'p.title' => [ + 'options' => ['label' => 'Title'], + ], + 'p.published' => [ + 'type' => ChoiceType::class, + 'options' => [ + 'label' => 'Published', + 'placeholder' => $this->translator->trans('All'), + 'choices' => [ + 'Published' => 1, + 'Unpublished' => 0, + ], + ], + ], + ] + ]; + } +} +``` + +--- + +## 4. LiveForm Component + +- Location: `src/Twig/Components/LiveForms/Live{Feature}FormComponent.php`. +- Uses `DefaultActionTrait`, `ComponentWithFormTrait`, `LiveAsyncFileInputTrait`. +- Initial form data property: `#[LiveProp] public ?Entity $initialFormData = null;`. +- Implements `instantiateForm()` and `#[LiveAction] save()`. + +```php +initialFormData) { + $this->initialFormData = new Page(); + } + return $this->createForm(PageFormType::class, $this->initialFormData, [ + 'attr' => [ + 'data-action' => 'live#action:prevent', + 'data-live-action-param' => 'save', + 'novalidate' => true, + ] + ]); + } + + #[LiveAction] + public function save() + { + $this->submitForm(); + /** @var Page $object */ + $object = $this->getForm()->getData(); + $isCreate = !boolval($object->getId()); + $this->entityManager->persist($object); + $this->entityManager->flush(); + + $this->addFlash('success', $this->translator->trans( + $isCreate ? 'Page created successfully.' : 'Page updated successfully.' + )); + + if ($isCreate) { + return $this->redirectToRoute('app_admin_page_edit', ['id' => $object->getId()]); + } + } +} +``` + +--- + +## 5. Templates Architecture + +### Index Page (`templates/admin/{feature}/index.html.twig`) +```twig +{% extends 'base.html.twig' %} + +{% block actions %} + + + {% trans %}Add New Page{% endtrans %} + +{% endblock %} + +{% block body %} + {{ component('PagesSearchComponent', {listTopic: 'pages', loading: 'lazy', entityClass: 'App\\Entity\\Page\\Page', options: {'allow_remove': true, 'allow_bulk_remove': true}}) }} +{% endblock %} +``` + +### Edit Page (`templates/admin/{feature}/edit.html.twig`) +```twig +{% extends 'admin/page/index.html.twig' %} + +{% block backLink %} + + + {% trans %}Back{% endtrans %} + +{% endblock %} + +{% block body %} +
+
+ {{ component('LiveForms:LivePageFormComponent', { initialFormData: page }) }} +
+
+ {% if page.id %} + {{ include('admin/page/_delete_form.html.twig') }} + {% endif %} +{% endblock %} +``` + +### Delete Form (`templates/admin/{feature}/_delete_form.html.twig`) +```twig +
+
+ + {% set modalId = 'delete-modal-' ~ page.id %} + +
+ {% include theme.themeDirectory ~ "/partials/_delete_modal.html.twig" with {modalId: modalId, objectName: "Page"|trans} %} +
+``` + +### Row Template (`templates/admin/{feature}/_row.html.twig`) +```twig +{% set action = action is defined ? action : null %} + + {% if bulkRemoveAllowed is defined and bulkRemoveAllowed %} + + + + {% else %} + + {% endif %} + +
+ {% if action != 'remove' %} + + + + {% if action == null %} +
+ + {% set modalId = 'delete-modal-' ~ object.id %} + + {% include theme.themeDirectory ~ "/partials/_delete_modal.html.twig" with {modalId: modalId, objectName: "Page"|trans} %} +
+ {% endif %} + {% endif %} +
+ + {{ object.id }} + {{ object.title }} + + {% if object.published %} + {% trans %}Published{% endtrans %} + {% else %} + {% trans %}Unpublished{% endtrans %} + {% endif %} + + + + + + + + +``` + +### LiveForm Twig Container (`templates/components/LiveForms/Live{Feature}FormComponent.html.twig`) +```twig +
+ {{ include(theme.themeDirectory ~ '/partials/_flash_messages.html.twig') }} + {% include "admin/page/_form.html.twig" %} +
+``` + +### Partial Form (`templates/admin/{feature}/_form.html.twig`) +```twig +{% set page = initialFormData %} +{% if page.id %} +
+ +
{{ page.createdAt|format_datetime('full', 'short') }}
+
+
+ +
{{ page.updatedAt|format_datetime('full', 'short') }}
+
+{% endif %} + +{{ form_start(form) }} +{{ form_widget(form) }} + +
+ +
+{{ form_end(form) }} +``` + +### Broadcast Turbo Stream (`templates/broadcast/{Entity}/{Entity}.stream.html.twig`) +```twig +{% block create %} + + + +{% endblock %} + +{% block update %} + + + +{% endblock %} + +{% block remove %} + + + +{% endblock %} +``` + +--- + +## 6. Sidebar Navigation + +Add menu items to `templates/themes/base_theme/partials/_sidebar.html.twig`: + +```twig +
  • + + + {% trans %}Pages{% endtrans %} + +
  • +``` + diff --git a/.cursor/rules/comfony.mdc b/.cursor/rules/comfony.mdc new file mode 100644 index 0000000..0d89d7f --- /dev/null +++ b/.cursor/rules/comfony.mdc @@ -0,0 +1,17 @@ +--- +description: Comfony project architecture, console execution, and conventions +globs: * +alwaysApply: true +--- + +Please strictly follow the project rules, architectural guidelines, and execution workflows defined in `AGENTS.md` at the repository root. + +- **Main Instructions:** [AGENTS.md](../../AGENTS.md) +- **CRUD & Component Templates:** [.agents/skills/comfony-crud/SKILL.md](../../.agents/skills/comfony-crud/SKILL.md) +- **daisyUI 5 Component Guide:** [.agents/skills/daisyui/SKILL.md](../../.agents/skills/daisyui/SKILL.md) + +## Key Directives +- **Console execution:** In Docker, use `./bin/docker-console.sh `; locally, use `./bin/console `. +- **Database:** NEVER create manual migrations (`make:migration`). Always use `./bin/docker-console.sh config:import`. +- **Entities:** Setters must accept nullable parameters for Symfony UX LiveComponent compatibility. + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 322fc70..81219f9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,84 +1,15 @@ -# Comfony - Symfony + Hotwire Stack Instructions +# Comfony - Agent & Copilot Instructions -Comfony is a modern Symfony 7.3 boilerplate with Hotwire Turbo, Stimulus, daisyUI/TailwindCSS, and UX Live Components for real-time reactive UIs. +Comfony is a modern Symfony 7.3 boilerplate with Hotwire Turbo, Stimulus, daisyUI 5, Tailwind CSS 4, and Symfony UX Live Components. -## Architecture Overview +## Core Rules & Single Source of Truth +Please strictly follow the complete architectural standards and commands defined in [AGENTS.md](../AGENTS.md): -**Frontend Stack**: Hotwire Turbo + Stimulus + TailwindCSS + daisyUI -**Backend**: Symfony 7.3 + Doctrine + UX Live Components -**Real-time**: Mercure for broadcasting entity changes -**Theme System**: Extensible theme directory structure with `theme.themeDirectory` variable +- **Console execution:** In Docker use `./bin/docker-console.sh `; locally use `./bin/console `. +- **Database changes:** NEVER create manual migrations (`make:migration`). Run `./bin/docker-console.sh config:import`. +- **Entities:** Setters must accept nullable parameters for UX LiveComponent compatibility. +- **Real-time updates:** Use `#[Broadcast(topics: ['...'], private: true)]` for Mercure streaming. -## Core Development Patterns - -### Entity Development (follows Doctrine + Broadcast patterns) -- Entities may use `TimestampableEntity` and `BlameableEntity` traits if needed. -- Add `#[Broadcast(topics: ['entityname'], private: true)]` for real-time updates if needed. -- Setters MUST accept nullable parameters for LiveComponent compatibility. -- File attachments use Many-to-Many or Many-To-One with `App\Entity\File\File` -- NO manual migrations - run `php bin/console config:import` for schema changes - -### CRUD Implementation (LiveComponent + Turbo pattern) -1. **Controller**: Standard Symfony controller with index/new/edit/delete routes -2. **Search Component**: Extends `DatatableComponent` with table config, filters, sorting -3. **LiveForm Component**: Uses `ComponentWithFormTrait` for reactive forms -4. **Templates**: - - Index extends base with search component - - Edit uses LiveForm with `initialFormData` - - Row templates for table display with actions -5. **Broadcast Stream**: `templates/broadcast/Entity/Entity.stream.html.twig` for real-time updates - -### LiveComponent Development -- Place in `App\Twig\Components` namespace -- Search components extend `DatatableComponent` base class -- Form components use `ComponentWithFormTrait` -- Use `LiveAsyncFileInputTrait` if form uses async file input.s -- Define `#[LiveProp(writable: true, url: true)]` for URL persistence -- Use `#[LiveAction]` methods for user interactions - -## Key Commands & Workflows - -```bash -# Development environment -docker compose up # Start full stack -yarn watch # Watch assets (or npm run watch) - -# Translations -symfony console translation:extract --force --format=yaml en -symfony console translation:extract --force --format=yaml tr - -# Database changes (no migrations!) -php bin/console config:import # Auto-apply schema changes -php bin/console config:dump # Dump configuration defiled in config/dump_config.yml - -# Code quality -phpcbf && phpcs # Fix + check code standards - -# Scheduling -symfony console schedule:list # List scheduled commands -symfony console schedule:run # Run scheduler -``` - -## Frontend Architecture -- **Stimulus Controllers**: Located in `assets/controllers/` -- **Build**: Webpack Encore with Tailwind CSS + daisyUI + PostCSS -- **Icons**: Tabler icons via `i-tabler-{name}` classes -- **Modals**: Use `data-controller="modal"` with modal targets -- **Real-time**: Turbo streams automatically update via Mercure broadcasts - -## File Organization Conventions -- **Controllers**: `src/Controller/{Feature}/` (e.g., `Admin/PageController.php`) -- **Entities**: `src/Entity/{Domain}/` (e.g., `Page/Page.php`) -- **Components**: `src/Twig/Components/{Feature}SearchComponent.php` -- **Templates**: `templates/{feature}/` with `_row.html.twig`, `_form.html.twig` partials -- **Theme Assets**: `templates/themes/base_theme/` (extensible theme system) - ---- -applyTo: "**" ---- -Apply the [comfony coding instructions](./instructions/comfony-instructions.md) to all code. - ---- -applyTo: "**/*.html.twig,**/*.twig,**/*.html" ---- -Apply the [daisyui coding instructions](./instructions/daisyui-instructions.md) to all files that contains html. \ No newline at end of file +## Specialized Skills & Templates +- **CRUD & Component Generation:** Follow [.agents/skills/comfony-crud/SKILL.md](../.agents/skills/comfony-crud/SKILL.md) for DatatableComponent, LiveForms, and Turbo Stream templates. +- **daisyUI 5 Components:** Follow [.agents/skills/daisyui/SKILL.md](../.agents/skills/daisyui/SKILL.md). \ No newline at end of file diff --git a/.github/instructions/comfony-instructions.md b/.github/instructions/comfony-instructions.md deleted file mode 100644 index 44f5432..0000000 --- a/.github/instructions/comfony-instructions.md +++ /dev/null @@ -1,577 +0,0 @@ -Comfony is a boilerplate created using symfony 7.3, turbo, stimulus. -Use PHP 8.4 coding standard. - - -# Entity definition - -- Use broadcast attibute if the entity updates should be streamed using mercure protocol. -- File storages should be defined using `App\Entity\File\File` entity. -- If an entity has many files definition should 'Many to Many' connection with File entity. -- File entity should not changed if it is not neccesseary. -- Setter methods should accept nullable parameters always because LiveComponent's require this. -- Validations should be defines using `Symfony\Component\Validator\Constraints` namespace. -- Do not define validator if not neccessary. -- Do not define migration file, comfony created database structure automatically. Just offer to run 'php bin/console config:import' if any database changes required. -```php -attachments = new ArrayCollection(); - } - - public function getId(): ?int - { - return $this->id; - } - - public function getTitle(): ?string - { - return $this->title; - } - - public function setTitle(?string $title): static - { - $this->title = $title; - - return $this; - } - - public function getBody(): ?string - { - return $this->body; - } - - public function setBody(?string $body): static - { - $this->body = $body; - - return $this; - } - - public function getSlug(): ?string - { - return $this->slug; - } - - public function setSlug(?string $slug): static - { - $this->slug = $slug; - - return $this; - } - - /** - * @return Collection - */ - public function getAttachments(): Collection - { - return $this->attachments; - } - - public function addAttachment(File $attachment): static - { - if (!$this->attachments->contains($attachment)) { - $this->attachments->add($attachment); - } - - return $this; - } - - public function removeAttachment(File $attachment): static - { - $this->attachments->removeElement($attachment); - - return $this; - } - - public function isPublished(): ?bool - { - return $this->published; - } - - public function setPublished(?bool $published): static - { - $this->published = $published; - - return $this; - } -} -``` - -# CRUD Operation for an entity - -- Define an index page - - Index page should follow this structure - - May change icons using i-tabler-{iconName} - - May change button text - - May change button url - - May add new buttons depending on requirement - - Should add Search component - - List topic is not mandatory, should match with entity's broadcat topic - - Loading lazy is required - - ```twig - {% extends 'base.html.twig' %} - - {% block actions %} - - - {% trans %}Add New Page{% endtrans %} - - {% endblock %} - - {% block body %} - {{ component('PagesSearchComponent', {listTopic: 'pages', loading: 'lazy', entityClass: 'App\\Entity\\Page\\Page', options: {'allow_remove': true, 'allow_bulk_remove': true}}) }} - {% endblock %} - ``` -- Define an edit page - - New page uses edit page with new entity - - Place backlink to index page - - Place LiveForm component with initialFormData attribute. - - include delete form - - ```twig - {% extends 'admin/page/index.html.twig' %} - {% block backLink %} - - - {% trans %}Back{% endtrans %} - - {% endblock %} - {% block body %} -
    -
    - {{ component('LiveForms:LivePageFormComponent', { initialFormData: page }) }} -
    -
    - {% if page.id %} - {{ include('admin/page/_delete_form.html.twig') }} - {% endif %} - {% endblock %} - ``` -- Define delete route - - Delete form should be like this - - Define form action path with entity id - - Create csrf token to validate in controller - - Inset a button to open delete modal. Modal automatically submits form after confirmation. - - ```twig -
    -
    - - {% set modalId = 'delete-modal-' ~ page.id %} - -
    - {% include theme.themeDirectory ~ "/partials/_delete_modal.html.twig" with {modalId: modalId, objectName: "Page"|trans} %} -
    - ``` - -```php -render('admin/page/index.html.twig', [ - 'title' => $this->translator->trans('Pages'), - ]); - } - - #[Route('/new', name: 'app_admin_page_new', methods: ['GET'])] - public function new(): Response - { - $page = new Page(); - return $this->render('admin/page/edit.html.twig', [ - 'title' => $this->translator->trans('Add New Page'), - 'page' => $page, - ]); - } - - #[Route('/{id}/edit', name: 'app_admin_page_edit', methods: ['GET'])] - public function edit(Page $page): Response - { - return $this->render('admin/page/edit.html.twig', [ - 'title' => $this->translator->trans('Edit Page {title}', ['title' => $page->getTitle()]), - 'page' => $page, - ]); - } - - #[Route('/{id}', name: 'app_admin_page_delete', methods: ['POST'])] - public function delete(Request $request, Page $page, EntityManagerInterface $entityManager): Response - { - if ($this->isCsrfTokenValid('delete' . $page->getId(), $request->request->get('_token'))) { - $entityManager->remove($page); - $entityManager->flush(); - $this->addFlash('success', $this->translator->trans('Page deleted successfully.')); - } - - return $this->redirectToRoute('app_admin_page_index', [], Response::HTTP_SEE_OTHER); - } -} - -``` - -## Create Search Component - -- Place search components under `App\Twig\Components` namespace. -- Define default sorting with `$sort` and `$direction` parameters. -- Define row template file with `$rowTemplateFile`. -- Set default filtering if required in `getQueryBuilder` method. Return non filtered qury if there is no default filters. -- Prepare table build data. - - headers: defines table headers - - quick_filters: defines quick search columns. Do not leave emtpy, try to insert at least one filter here. - - filters: defines detailed filters. -```php -entityManager->getRepository(Page::class)->createQueryBuilder('p'); - } - - #[Override] - public function getTableBuildData(): array - { - return [ - 'headers' => [ - [ - 'label' => $this->translator->trans("Actions") - ], - 'p.id' => [ - 'label' => $this->translator->trans("Id"), - 'sortable' => true, - ], - 'p.title' => [ - 'label' => $this->translator->trans("Title"), - 'sortable' => true, - ], - [ - 'label' => $this->translator->trans('Published'), - ], - 'p.createdAt' => [ - 'label' => $this->translator->trans('created_at'), - 'sortable' => true - ], - 'p.updatedAt' => [ - 'label' => $this->translator->trans('updated_at'), - 'sortable' => true - ] - ], - "quick_filters" => [ - "p.title" => [] - ], - "filters" => [ - 'p.title' => [ - 'options' => [ - 'label' => 'Title', - ], - ], - 'p.published' => [ - 'type' => ChoiceType::class, - 'options' => [ - 'label' => 'Published', - 'placeholder' => $this->translator->trans('All'), - 'choices' => [ - 'Published' => 1, - 'Unpublished' => 0, - ], - ], - ], - ] - ]; - } -} -``` -Row file should something like this: - -```html - {% set action = action is defined ? action : null %} - - {% if bulkRemoveAllowed is defined and bulkRemoveAllowed %} - - - - {% else %} - - {% endif %} - -
    - {% if action != 'remove' %} - - - - {% if action == null %} -
    - - {% set modalId = 'delete-modal-' ~ object.id %} - - {% include theme.themeDirectory ~ "/partials/_delete_modal.html.twig" with {modalId: modalId, objectName: "Page"|trans} %} -
    - {% endif %} - {% endif %} -
    - - {{ object.id }} - {{ object.title }} - - {% if object.published %} - - {% trans %}Published{% endtrans %} - - {% else %} - - {% trans %}Unpublished{% endtrans %} - - {% endif %} - - - - - - - - - -``` - -If entity uses broadcast attribute creating a stream file is required. And should something like this. -`templates/broadcast/Page/Page.stream.html.twig` -```twig -{% block create %} - - - -{% endblock %} - -{% block update %} - - - -{% endblock %} - -{% block remove %} - - - -{% endblock %} -``` - -## Create Live Form Component - -Use symfony live components to create live forms. Try to use live forms as possible to create entity forms. -Setup success or error messages depending on entity and action. - -```php -initialFormData) { - $this->initialFormData = new Page(); - } - $form = $this->createForm(PageFormType::class, $this->initialFormData, [ - 'attr' => [ - 'data-action' => 'live#action:prevent', - 'data-live-action-param' => 'save', - 'novalidate' => true - ] - ]); - return $form; - } - - #[LiveAction] - public function save() - { - $this->submitForm(); - /** @var Page */ - $object = $this->getForm()->getData(); - $isCreate = !boolval($object->getId()); - $this->entityManager->persist($object); - $this->entityManager->flush(); - - $this->addFlash('success', $this->translator->trans( - $isCreate ? 'Page created successfully.' : 'Page updated successfully.' - )); - if($isCreate){ - return $this->redirectToRoute('app_admin_page_edit', ['id' => $object->getId()]); - } - } -} -``` - -Create a twig file for entity like this in `templates/components/LiveForms`: - -```twig -
    - {{ include(theme.themeDirectory ~ '/partials/_flash_messages.html.twig') }} - {% include "admin/page/_form.html.twig" %} -
    -``` - -Setup default form like this in `templates/{entityPath}/_form.html.twig` - -```twig -{% set page = initialFormData %} -{% if page.id %} -
    - -
    {{ page.createdAt|format_datetime('full', 'short') }}
    -
    -
    - -
    {{ page.updatedAt|format_datetime('full', 'short') }}
    -
    -{% endif %} -{{ form_start(form) }} -{{ form_widget(form) }} -
    - -
    -{{ form_end(form) }} - -``` - -# Add link to sidebar - -Add proper link in proper location in `templates/themes/base_theme/partials/_sidebar.html.twig` with proper access checks if required. -For example page link like this: - -
  • - - - {% trans %}Pages{% endtrans %} - -
  • diff --git a/.github/instructions/daisyui-instructions.md b/.github/instructions/daisyui-instructions.md deleted file mode 100644 index 853be7e..0000000 --- a/.github/instructions/daisyui-instructions.md +++ /dev/null @@ -1,1851 +0,0 @@ ---- -description: daisyUI 5 -alwaysApply: true -applyTo: "**" -downloadedFrom: https://daisyui.com/llms.txt -version: 5.5.x ---- - -# daisyUI 5 -daisyUI 5 is a CSS library for Tailwind CSS 4 -daisyUI 5 provides class names for common UI components - -- [daisyUI 5 docs](http://daisyui.com) -- [Guide: How to use this file in LLMs and code editors](https://daisyui.com/docs/editor/) -- [daisyUI 5 release notes](https://daisyui.com/docs/v5/) -- [daisyUI 4 to 5 upgrade guide](https://daisyui.com/docs/upgrade/) - -## daisyUI 5 install notes -[install guide](https://daisyui.com/docs/install/) -1. daisyUI 5 requires Tailwind CSS 4 -2. `tailwind.config.js` file is deprecated in Tailwind CSS v4. do not use `tailwind.config.js`. Tailwind CSS v4 only needs `@import "tailwindcss";` in the CSS file if it's a node dependency. -3. daisyUI 5 can be installed using `npm i -D daisyui@latest` and then adding `@plugin "daisyui";` to the CSS file -4. daisyUI is suggested to be installed as a dependency but if you really want to use it from CDN, you can use Tailwind CSS and daisyUI CDN files: -```html - - -``` -5. A CSS file with Tailwind CSS and daisyUI looks like this (if it's a node dependency) -```css -@import "tailwindcss"; -@plugin "daisyui"; -``` - -## daisyUI 5 usage rules -1. We can give styles to a HTML element by adding daisyUI class names to it. By adding a component class name, part class names (if there's any available for that component), and modifier class names (if there's any available for that component) -2. Components can be customized using Tailwind CSS utility classes if the customization is not possible using the existing daisyUI classes. For example `btn px-10` sets a custom horizontal padding to a `btn` -3. If customization of daisyUI styles using Tailwind CSS utility classes didn't work because of CSS specificity issues, you can use the `!` at the end of the Tailwind CSS utility class to override the existing styles. For example `btn bg-red-500!` sets a custom background color to a `btn` forcefully. This is a last resort solution and should be used sparingly -4. If a specific component or something similar to it doesn't exist in daisyUI, you can create your own component using Tailwind CSS utility -5. when using Tailwind CSS `flex` and `grid` for layout, it should be responsive using Tailwind CSS responsive utility prefixes. -6. Only allowed class names are existing daisyUI class names or Tailwind CSS utility classes. -7. Ideally, you won't need to write any custom CSS. Using daisyUI class names or Tailwind CSS utility classes is preferred. -8. suggested - if you need placeholder images, use https://picsum.photos/200/300 with the size you want -9. suggested - when designing , don't add a custom font unless it's necessary -10. don't add `bg-base-100 text-base-content` to body unless it's necessary -11. For design decisions, use Refactoring UI book best practices - -daisyUI 5 class names are one of the following categories. These type names are only for reference and are not used in the actual code -- `component`: the required component class -- `part`: a child part of a component -- `style`: sets a specific style to component or part -- `behavior`: changes the behavior of component or part -- `color`: sets a specific color to component or part -- `size`: sets a specific size to component or part -- `placement`: sets a specific placement to component or part -- `direction`: sets a specific direction to component or part -- `modifier`: modifies the component or part in a specific way -- `variant`: prefixes for utility classes that conditionally apply styles. syntax is `variant:utility-class` - -## Config -daisyUI 5 config docs: https://daisyui.com/docs/config/ -daisyUI without config: -```css -@plugin "daisyui"; -``` -daisyUI config with `light` theme only: -```css -@plugin "daisyui" { - themes: light --default; -} -``` -daisyUI with all the default configs: -```css -@plugin "daisyui" { - themes: light --default, dark --prefersdark; - root: ":root"; - include: ; - exclude: ; - prefix: ; - logs: true; -} -``` -An example config: -In below config, all the built-in themes are enabled while bumblebee is the default theme and synthwave is the prefersdark theme (default dark mode) -All the other themes are enabled and can be used by adding `data-theme="THEME_NAME"` to the `` element -root scrollbar gutter is excluded. `daisy-` prefix is used for all daisyUI classes and console.log is disabled -```css -@plugin "daisyui" { - themes: light, dark, cupcake, bumblebee --default, emerald, corporate, synthwave --prefersdark, retro, cyberpunk, valentine, halloween, garden, forest, aqua, lofi, pastel, fantasy, wireframe, black, luxury, dracula, cmyk, autumn, business, acid, lemonade, night, coffee, winter, dim, nord, sunset, caramellatte, abyss, silk; - root: ":root"; - include: ; - exclude: rootscrollgutter, checkbox; - prefix: daisy-; - logs: false; -} -``` -## daisyUI 5 colors - -### daisyUI color names -- `primary`: Primary brand color, The main color of your brand -- `primary-content`: Foreground content color to use on primary color -- `secondary`: Secondary brand color, The optional, secondary color of your brand -- `secondary-content`: Foreground content color to use on secondary color -- `accent`: Accent brand color, The optional, accent color of your brand -- `accent-content`: Foreground content color to use on accent color -- `neutral`: Neutral dark color, For not-saturated parts of UI -- `neutral-content`: Foreground content color to use on neutral color -- `base-100`:-100 Base surface color of page, used for blank backgrounds -- `base-200`:-200 Base color, darker shade, to create elevations -- `base-300`:-300 Base color, even more darker shade, to create elevations -- `base-content`: Foreground content color to use on base color -- `info`: Info color, For informative/helpful messages -- `info-content`: Foreground content color to use on info color -- `success`: Success color, For success/safe messages -- `success-content`: Foreground content color to use on success color -- `warning`: Warning color, For warning/caution messages -- `warning-content`: Foreground content color to use on warning color -- `error`: Error color, For error/danger/destructive messages -- `error-content`: Foreground content color to use on error color - -### daisyUI color rules -1. daisyUI adds semantic color names to Tailwind CSS colors -2. daisyUI color names can be used in utility classes, like other Tailwind CSS color names. for example, `bg-primary` will use the primary color for the background -3. daisyUI color names include variables as value so they can change based the theme -4. There's no need to use `dark:` for daisyUI color names -5. Ideally only daisyUI color names should be used for colors so the colors can change automatically based on the theme -6. If a Tailwind CSS color name (like `red-500`) is used, it will be same red color on all themes -7. If a daisyUI color name (like `primary`) is used, it will change color based on the theme -8. Using Tailwind CSS color names for text colors should be avoided because Tailwind CSS color `text-gray-800` on `bg-base-100` would be unreadable on a dark theme - because on dark theme, `bg-base-100` is a dark color -9. `*-content` colors should have a good contrast compared to their associated colors -10. suggestion - when designing a page use `base-*` colors for majority of the page. use `primary` color for important elements - -### daisyUI custom theme with custom colors -A CSS file with Tailwind CSS, daisyUI and a custom daisyUI theme looks like this: -```css -@import "tailwindcss"; -@plugin "daisyui"; -@plugin "daisyui/theme" { - name: "mytheme"; - default: true; /* set as default */ - prefersdark: false; /* set as default dark mode (prefers-color-scheme:dark) */ - color-scheme: light; /* color of browser-provided UI */ - - --color-base-100: oklch(98% 0.02 240); - --color-base-200: oklch(95% 0.03 240); - --color-base-300: oklch(92% 0.04 240); - --color-base-content: oklch(20% 0.05 240); - --color-primary: oklch(55% 0.3 240); - --color-primary-content: oklch(98% 0.01 240); - --color-secondary: oklch(70% 0.25 200); - --color-secondary-content: oklch(98% 0.01 200); - --color-accent: oklch(65% 0.25 160); - --color-accent-content: oklch(98% 0.01 160); - --color-neutral: oklch(50% 0.05 240); - --color-neutral-content: oklch(98% 0.01 240); - --color-info: oklch(70% 0.2 220); - --color-info-content: oklch(98% 0.01 220); - --color-success: oklch(65% 0.25 140); - --color-success-content: oklch(98% 0.01 140); - --color-warning: oklch(80% 0.25 80); - --color-warning-content: oklch(20% 0.05 80); - --color-error: oklch(65% 0.3 30); - --color-error-content: oklch(98% 0.01 30); - - --radius-selector: 1rem; /* border radius of selectors (checkbox, toggle, badge) */ - --radius-field: 0.25rem; /* border radius of fields (button, input, select, tab) */ - --radius-box: 0.5rem; /* border radius of boxes (card, modal, alert) */ - /* preferred values for --radius-* : 0rem, 0.25rem, 0.5rem, 1rem, 2rem */ - - --size-selector: 0.25rem; /* base size of selectors (checkbox, toggle, badge). Value must be 0.25rem unless we intentionally want bigger selectors. In so it can be 0.28125 or 0.3125. If we intentionally want smaller selectors, it can be 0.21875 or 0.1875 */ - --size-field: 0.25rem; /* base size of fields (button, input, select, tab). Value must be 0.25rem unless we intentionally want bigger fields. In so it can be 0.28125 or 0.3125. If we intentionally want smaller fields, it can be 0.21875 or 0.1875 */ - - --border: 1px; /* border size. Value must be 1px unless we intentionally want thicker borders. In so it can be 1.5px or 2px. If we intentionally want thinner borders, it can be 0.5px */ - - --depth: 1; /* only 0 or 1 – Adds a shadow and subtle 3D depth effect to components */ - --noise: 0; /* only 0 or 1 - Adds a subtle noise (grain) effect to components */ -} -``` -#### Rules -- All CSS variables above are required -- Colors can be OKLCH or hex or other formats -- If you're generating a custom theme, do not include the comments from the example above. Just provide the code. - -People can use https://daisyui.com/theme-generator/ visual tool to create their own theme. - -## daisyUI 5 components - -### accordion -Accordion is used for showing and hiding content but only one item can stay open at a time - -[accordion docs](https://daisyui.com/components/accordion/) - -#### Class names -- component: `collapse` -- part: `collapse-title`, `collapse-content` -- modifier: `collapse-arrow`, `collapse-plus`, `collapse-open`, `collapse-close` - -#### Syntax -```html -
    {CONTENT}
    -``` -where content is: -```html - -
    {title}
    -
    {CONTENT}
    -``` - -#### Rules -- {MODIFIER} is optional and can have one of the modifier class names -- Accordion uses radio inputs. All radio inputs with the same name work together and only one of them can be open at a time -- If you have more than one set of accordion items on a page, use different names for the radio inputs on each set -- Replace {name} with a unique name for the accordion group -- replace `{checked}` with `checked="checked"` if you want the accordion to be open by default - -### alert -Alert informs users about important events - -[alert docs](https://daisyui.com/components/alert/) - -#### Class names -- component: `alert` -- style: `alert-outline`, `alert-dash`, `alert-soft` -- color: `alert-info`, `alert-success`, `alert-warning`, `alert-error` -- direction: `alert-vertical`, `alert-horizontal` - -#### Syntax -```html - -``` - -#### Rules -- {MODIFIER} is optional and can have one of each style/color/direction class names -- Add `sm:alert-horizontal` for responsive layouts - -### avatar -Avatars are used to show a thumbnail - -[avatar docs](https://daisyui.com/components/avatar/) - -#### Class names -- component: `avatar`, `avatar-group` -- modifier: `avatar-online`, `avatar-offline`, `avatar-placeholder` - -#### Syntax -```html -
    -
    - -
    -
    -``` - -#### Rules -- {MODIFIER} is optional and can have one of the modifier class names -- Use `avatar-group` for containing multiple avatars -- You can set custom sizes using `w-*` and `h-*` -- You can use mask classes such as `mask-squircle`, `mask-hexagon`, `mask-triangle` - -### badge -Badges are used to inform the user of the status of specific data - -[badge docs](https://daisyui.com/components/badge/) - -#### Class names -- component: `badge` -- style: `badge-outline`, `badge-dash`, `badge-soft`, `badge-ghost` -- color: `badge-neutral`, `badge-primary`, `badge-secondary`, `badge-accent`, `badge-info`, `badge-success`, `badge-warning`, `badge-error` -- size: `badge-xs`, `badge-sm`, `badge-md`, `badge-lg`, `badge-xl` - -#### Syntax -```html -Badge -``` - -#### Rules -- {MODIFIER} is optional and can have one of each style/color/size class names -- Can be used inside text or buttons -- To create an empty badge, just remove the text between the span tags - -### breadcrumbs -Breadcrumbs helps users to navigate - -[breadcrumbs docs](https://daisyui.com/components/breadcrumbs/) - -#### Class names -- component: `breadcrumbs` - -#### Syntax -```html - -``` - -#### Rules -- breadcrumbs only has one main class name -- Can contain icons inside the links -- If you set `max-width` or the list gets larger than the container it will scroll - -### button -Buttons allow the user to take actions - -[button docs](https://daisyui.com/components/button/) - -#### Class names -- component: `btn` -- color: `btn-neutral`, `btn-primary`, `btn-secondary`, `btn-accent`, `btn-info`, `btn-success`, `btn-warning`, `btn-error` -- style: `btn-outline`, `btn-dash`, `btn-soft`, `btn-ghost`, `btn-link` -- behavior: `btn-active`, `btn-disabled` -- size: `btn-xs`, `btn-sm`, `btn-md`, `btn-lg`, `btn-xl` -- modifier: `btn-wide`, `btn-block`, `btn-square`, `btn-circle` - -#### Syntax -```html - -``` -#### Rules -- {MODIFIER} is optional and can have one of each color/style/behavior/size/modifier class names -- btn can be used on any html tags such as ` -``` - -#### Rules -- {MODIFIER} is optional and can have one of the size class names -- To make a button active, add `dock-active` class to the button -- add `` is required for responsivness of the dock in iOS - -### drawer -Drawer is a grid layout that can show/hide a sidebar on the left or right side of the page - -[drawer docs](https://daisyui.com/components/drawer/) - -#### Class names -- component: `drawer` -- part: `drawer-toggle`, `drawer-content`, `drawer-side`, `drawer-overlay` -- placement: `drawer-end` -- modifier: `drawer-open` -- variant: `is-drawer-open:`, `is-drawer-close:` - -#### Syntax -```html -
    - -
    {CONTENT}
    -
    {SIDEBAR}
    -
    -``` -where {CONTENT} can be navbar, site content, footer, etc -and {SIDEBAR} can be a menu like: -```html -
    -``` -To open/close the drawer, use a label that points to the `drawer-toggle` input: -```html - -``` -Example: This sidebar is always visible on large screen, can be toggled on small screen: -```html -
    - -
    - - -
    -
    - - -
    -
    -``` - -Example: This sidebar is always visible. When it's close we only see iocns, when it's open we see icons and text -```html -
    - -
    - -
    -
    - -
    - - - -
    - -
    -
    -
    -
    -``` - -#### Rules -- {MODIFIER} is optional and can have one of the modifier/placement class names -- `id` is required for the `drawer-toggle` input. change `my-drawer` to a unique id according to your needs -- `lg:drawer-open` can be used to make sidebar visible on larger screens -- `drawer-toggle` is a hidden checkbox. Use label with "for" attribute to toggle state -- if you want to open the drawer when a button is clicked, use `` where `my-drawer` is the id of the `drawer-toggle` input -- when using drawer, every page content must be inside `drawer-content` element. for example navbar, footer, etc should not be outside of `drawer` - -### dropdown -Dropdown can open a menu or any other element when the button is clicked - -[dropdown docs](https://daisyui.com/components/dropdown/) - -#### Class names -- component: `dropdown` -- part: `dropdown-content` -- placement: `dropdown-start`, `dropdown-center`, `dropdown-end`, `dropdown-top`, `dropdown-bottom`, `dropdown-left`, `dropdown-right` -- modifier: `dropdown-hover`, `dropdown-open`, `dropdown-close` - -#### Syntax -Using details and summary -```html - -``` - -Using popover API -```html - - -``` - -Using CSS focus -```html - -``` - -#### Rules -- {MODIFIER} is optional and can have one of the modifier/placement class names -- replace `{id}` and `{anchor}` with a unique name -- For CSS focus dropdowns, use `tabindex="0"` and `role="button"` on the button -- The content can be any HTML element (not just `
      `) - -### fab -FAB (Floating Action Button) stays in the bottom corner of screen. It includes a focusable and accessible element with button role. Clicking or focusing it shows additional buttons (known as Speed Dial buttons) in a vertical arrangement or a flower shape (quarter circle) - -[fab docs](https://daisyui.com/components/fab/) - -#### Class names -- component: `fab` -- part: `fab-close`, `fab-main-action` -- modifier: `fab-flower` - -#### Syntax -A single FAB in the corder of screen -```html -
      - -
      -``` -A FAB that opens a 3 other buttons in the corner of page vertically -```html -
      -
      {IconOriginal}
      - - - -
      -``` -A FAB that opens a 3 other buttons in the corner of page vertically and they have label text -```html -
      -
      {IconOriginal}
      -
      {Label1}
      -
      {Label2}
      -
      {Label3}
      -
      -``` -FAB with rectangle buttons. These are not circular buttons so they can have more content. -```html -
      -
      {IconOriginal}
      - - - -
      -``` -FAB with close button. When FAB is open, the original button is replaced with a close button -```html -
      -
      {IconOriginal}
      -
      Close ✕
      -
      {Label1}
      -
      {Label2}
      -
      {Label3}
      -
      -``` -FAB with Main Action button. When FAB is open, the original button is replaced with a main action button -```html -
      -
      {IconOriginal}
      -
      - {LabelMainAction} -
      -
      {Label1}
      -
      {Label2}
      -
      {Label3}
      -
      -``` -FAB Flower. It opens the buttons in a flower shape (quarter circle) arrangement instead of vertical -```html -
      -
      {IconOriginal}
      - - - - -
      -``` -FAB Flower with tooltips. There's no space for a text label in a quarter circle, so tooltips are used to indicate the button's function -```html -
      -
      {IconOriginal}
      - -
      - -
      -
      - -
      -
      - -
      -
      -``` -#### Rules -- {Icon*} should be replaced with the appropriate icon for each button. SVG icons are recommended -- {IconOriginal} is the icon that we see before opening the FAB -- {IconMainAction} is the icon we see after opening the FAB -- {Icon1}, {Icon2}, {Icon3} are the icons for the additional buttons -- {Label*} is the label text for each button - -### fieldset -Fieldset is a container for grouping related form elements. It includes fieldset-legend as a title and label as a description - -[fieldset docs](https://daisyui.com/components/fieldset/) - -#### Class names -- Component: `fieldset`, `label` -- Parts: `fieldset-legend` - -#### Syntax -```html -
      - {title} - {CONTENT} -

      {description}

      -
      -``` - -#### Rules -- You can use any element as a direct child of fieldset to add form elements - -### file-input -File Input is a an input field for uploading files - -[file-input docs](https://daisyui.com/components/file-input/) - -#### Class Names: -- Component: `file-input` -- Style: `file-input-ghost` -- Color: `file-input-neutral`, `file-input-primary`, `file-input-secondary`, `file-input-accent`, `file-input-info`, `file-input-success`, `file-input-warning`, `file-input-error` -- Size: `file-input-xs`, `file-input-sm`, `file-input-md`, `file-input-lg`, `file-input-xl` - -#### Syntax -```html - -``` - -#### Rules -- {MODIFIER} is optional and can have one of each style/color/size class names - -### filter -Filter is a group of radio buttons. Choosing one of the options will hide the others and shows a reset button next to the chosen option - -[filter docs](https://daisyui.com/components/filter/) - -#### Class names -- component: `filter` -- part: `filter-reset` - -#### Syntax -Using HTML form -```html -
      - - - -
      -``` -Without HTML form -```html -
      - - - -
      -``` - -#### Rules -- replace `{NAME}` with proper value, according to the context of the filter -- Each set of radio inputs must have unique `name` attributes to avoid conflicts -- Use `
      ` tag when possible and only use `
      ` if you can't use a HTML form for some reason -- Use `filter-reset` class for the reset button - -### footer -Footer can contain logo, copyright notice, and links to other pages - -[footer docs](https://daisyui.com/components/footer/) - -#### Class names -- component: `footer` -- part: `footer-title` -- placement: `footer-center` -- direction: `footer-horizontal`, `footer-vertical` - -#### Syntax -```html -
      {CONTENT}
      -``` -where content can contain several `