FormDevExpress Buton yapısı değiştirildi

This commit is contained in:
Sedat ÖZTÜRK 2026-09-07 09:14:00 +03:00
parent 59ec8950a3
commit 9b9ef9c100
7 changed files with 334 additions and 215 deletions

View file

@ -294,7 +294,7 @@ the Wizard's `Custom` path.
per-command Form permissions (`selectPermission`, `insertPermission`, `updatePermission`,
`deletePermission`). In `Otomatik` mode the base is the read permission the Wizard created for
the component and the commands get `''` / `.Create` / `.Update` / `.Delete` suffixes.
- List screen permissions: `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete}`.
- List screen permissions: `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`.
- Every save/delete is mirrored into `configs/seeds/{scope}/data/App.DeveloperKit.CustomComponents.json`; the endpoints it uses are
referenced through `crud/{Entity}.json`. A component that is not seed-backed is not done.
@ -657,10 +657,12 @@ a deploy. AI must learn and teach implementation flow primarily from these asset
- LanguagesData.json
- WizardDataSeeder.cs / CrudDataSeeder.cs — the seeders that read
surface B below
- ListFormSeedDataApplier (Domain/Data/) — applies `data/{ListFormCode}.json` after every
contributor has run
**B. Runtime seeds (written by the running application)** — `configs/seeds/`, resolved through
`SeedPathResolver`. The Wizard, the Custom Components screen / Visual Designer and the CRUD
Endpoint Manager write here; the
`SeedPathResolver`. The Wizard, the Custom Components screen / Visual Designer, the CRUD
Endpoint Manager and every ListForm screen with a `SeedFilePath` write here; the
seeders above restore them when the database is recreated. Scope folders mirror the CDN layout:
```

View file

@ -1490,6 +1490,9 @@ Kurallar:
- `AuthName` boş bırakılırsa buton **herkese görünür**. Süreç butonlarında bu alanı boş bırakma:
yetki kontrolünden geçmeyen buton hiç çizilmez, gizlemenin tek doğru yolu budur.
- `Text` ve `Hint` dil anahtarıdır; çeviri `::` öneki eklenerek çözülür. Düz metin yazma.
- `Icon` bir **DevExtreme ikon adıdır** (`check`, `trash`, `doc`, `preferences` …); tema CSS'i
`dx-icon-<ad>` sınıfıyla çizer. Komut diyalogundaki alan hazır ikon listesini önerir, listede
olmayan bir ad da yazılabilir. Form ekranının araç çubuğu da aynı ikon adlarını kullanır.
- Toolbar butonu **seçili satırlarla** çalışır; `SelectionJson` uygun modda (`single`/`multiple`)
olmalıdır. İş akışı varsa wizard bunu zaten `single` yapar.
- Satır bazlı aksiyon istiyorsan `ButtonPosition: 0` kullan; buton komut sütununda görünür.
@ -2356,7 +2359,7 @@ Form düğümünün `id`'si) ve Save/Delete üzerinden geri yazar. Olaylar: `onL
- `Özel` mod: yetki adı elle yazılır.
- Boş bırakılan komut serbesttir.
Liste ekranı yetkileri: `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete}`.
Liste ekranı yetkileri: `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`.
### 7.6 Bileşeni menüye bağlama
@ -2892,9 +2895,11 @@ Endpoint **tanımlarını** düzenleme yetkileri ListForm ekranının
`App.DeveloperKit.CustomEndpoints{,.Create,.Update,.Delete}` yetkileridir — çağırma
yetkilerinden (`.Get/.Post/.Put/.Remove` + `PermissionsJson`) ayrıdır.
**Seed:** ekrandan girilen kayıt yalnızca veritabanında yaşar. Proje teslimi için kaydı
`sql/execute/{Ad}.sql` upsert prosedürüyle geri getirilebilir yap (§9.14 uyarısı); platform
varsayılanı ise çekirdeğin `Seeds/TenantData.json``CustomEndpoints` bloğundadır.
**Seed:** platform varsayılanları `configs/seeds/{kapsam}/data/App.DeveloperKit.CustomEndpoints.json`
dosyasından gelir; ekranın `SeedSyncInsert/Update/Delete` bayrakları **kapalıdır**, yani ekrandan
girilen kayıt dosyaya yansımaz ve yalnızca veritabanında yaşar. Proje teslimi için kaydı ya bu
dosyaya elle ekle ya da `sql/execute/{Ad}.sql` upsert prosedürüyle geri getirilebilir yap
(§9.14 uyarısı).
**Ne zaman CRUD, ne zaman Custom Endpoint?** Tek tabloya standart CRUD gerekiyorsa CRUD Endpoint
(otomatik üretilir, `crud/{Entity}.json` ile seed'lenir, filtre sözleşmesi hazırdır).

View file

@ -632,8 +632,8 @@ Teknik kullanıcılar için `/admin/developerkit` altında toplanan araçlar:
| **Custom Endpoint** | `/admin/list/App.DeveloperKit.CustomEndpoints` | SQL veya servis tanımından REST endpoint üretimi; GET/POST/PUT/DELETE için ayrı yetki kapıları (`App.DeveloperKit.CustomEndpoints.*`) ve kullanıcı/rol/global erişim kuralları. |
| **CRUD Endpoint** | SQL Query Manager içinde | Tablo tanımından tam CRUD endpoint kümesi üretimi. Ayrı ekranı yoktur; bkz. aşağıdaki not. |
| **Dynamic Service** | `/admin/list/App.DeveloperKit.DynamicServices` | Liste bir ListForm ekranıdır (KPI şeridi, Swagger butonu, kart/ızgara görünümü); kod yazma listeden açılan `DynamicServiceEditor` diyalogundadır: Monaco editörü, Roslyn ile derleme (`TestCompile`), yayınlama (`Publish`) ve çalışan uygulamaya controller olarak kaydetme. Yetkiler: Create/Edit/Delete/Manage/TestCompile/Publish/ViewCode. |
| **Custom Component** | `/admin/list/App.DeveloperKit.CustomComponents` | React bileşenini veritabanında saklama; `@babel/standalone` ile tarayıcıda derleyip route'a bağlama. Liste bir ListForm ekranıdır (KPI şeridi, ızgara/kart görünümü, grid'in kendi ekleme/düzenleme formu); satırdaki **Design** butonu `/admin/developerkit/components/design/{id}` tasarımcısını, **Open URL** butonu bileşenin `RoutePath`'ini açar. Kayıtlar `configs/seeds/host/data/App.DeveloperKit.CustomComponents.json` dosyasına senkronlanır. Yetkiler `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete}`. |
| **Visual Designer** | Component editörü içinde | Sürükle-bırak kanvas ile bileşen üretimi ve kod üretimi (`visualDesigner/codeGenerator.ts`). |
| **Custom Component** | `/admin/list/App.DeveloperKit.CustomComponents` | React bileşenini veritabanında saklama; `@babel/standalone` ile tarayıcıda derleyip route'a bağlama. Liste bir ListForm ekranıdır (KPI şeridi, ızgara/kart görünümü, grid'in kendi ekleme/düzenleme formu); satırdaki **Design** butonu `/admin/developerkit/components/design/{id}` tasarımcısını, **Open URL** butonu bileşenin `RoutePath`'ini açar. Kayıtlar `configs/seeds/host/data/App.DeveloperKit.CustomComponents.json` dosyasına senkronlanır. Yetkiler `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`. |
| **Visual Designer** | `/admin/developerkit/components/design/:id` | Custom Component listesindeki **Design** butonundan açılır; sürükle-bırak kanvas ile bileşen üretimi ve kod üretimi (`visualDesigner/codeGenerator.ts`). |
| **ListForm** | `/admin/list/App.Listforms.Listform` | Var olan ekranların ham tanım listesi; buradan `/admin/listform/edit/{kod}` sekmeli editörüne geçilir. |
| **Wizard Manager** | `/admin/listform/wizardManager` | Üretilmiş wizard seed dosyalarının listesi/kartı, düzenleme, silme, DB migrate, export/import. Yetki `App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}`. |
@ -783,7 +783,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
| Grup | Alt yetkiler |
| --- | --- |
| `App.Listforms.Wizard` | `.Create`, `.Update`, `.Delete`, `.Export`, `.Import` |
| `App.DeveloperKit.CustomComponents` | `.Create`, `.Update`, `.Delete` |
| `App.DeveloperKit.CustomComponents` | `.Create`, `.Update`, `.Delete`, `.Export`, `.Import` |
| `App.DeveloperKit.CustomEndpoints` | `.Get`, `.Post`, `.Put`, `.Remove` (dispatcher üzerinden çağrı kapısı; endpoint bazlı User/Role/Global kuralları bunun üstünde denetlenir) |
| `App.DeveloperKit.DynamicServices` | `.Create`, `.Edit`, `.Delete`, `.Manage`, `.TestCompile`, `.Publish`, `.ViewCode` |
| `App.SqlQueryManager` | `.CrudEndpoints` |
@ -1197,9 +1197,12 @@ derlenip yayınlanır. Yeni bir modülün nasıl kurulacağını öğrenmek içi
- `LanguagesData.json` — dil anahtarları (EN/TR)
- `WizardDataSeeder.cs`, `CrudDataSeeder.cs` — aşağıdaki
runtime seed dosyalarını okuyup uygulayan seeder'lar
- `ListFormSeedDataApplier` (`Domain/Data/`) — bütün contributor'lar bittikten sonra
`data/{ListFormCode}.json` dosyalarını uygular
**2. Runtime'da üretilen seed'ler** — `configs/seeds/`. Wizard, Custom Components ekranı/Visual
Designer ve CRUD Endpoint Manager çalışma zamanında burayı yazar; veritabanı silinip yeniden oluşturulduğunda aynı dosyalar
Designer, CRUD Endpoint Manager ve `SeedFilePath` tanımlı ListForm ekranları çalışma zamanında
burayı yazar; veritabanı silinip yeniden oluşturulduğunda aynı dosyalar
geri yüklenir. Kapsam klasörü CDN düzeniyle aynıdır ve `SeedPathResolver` üzerinden çözülür:
```
@ -1207,6 +1210,7 @@ configs/seeds/
├── host/ # host kapsamı
│ ├── wizard/ # {Ad}.json → WizardDataSeeder
│ ├── crud/ # {EntityName}.json → CrudDataSeeder
│ ├── data/ # {ListFormCode}.json → ListFormSeedDataApplier
│ ├── sql/{object,execute}/ # .sql (SQL Server)
│ └── postgres/{object,execute}/
├── tenants/{tenantId}/… # aynı düzen, tenant kapsamı

View file

@ -18,9 +18,10 @@ import { SelectBoxOption } from '@/types/shared'
import { useStoreActions, useStoreState } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik'
import CreatableSelect from 'react-select/creatable'
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
import { number, object, string } from 'yup'
import { commandButtonPositionTypeOptions, urlOptions } from '../options'
import { commandButtonIconOptions, commandButtonPositionTypeOptions, urlOptions } from '../options'
import { JsonRowDialogData } from './types'
import {
deleteListFormJsonRow,
@ -248,13 +249,34 @@ function JsonRowOpDialogCommand({
invalid={errors.icon && touched.icon}
errorMessage={errors.icon}
>
<Field
type="text"
autoComplete="off"
name="icon"
placeholder="Icon"
component={Input}
/>
<Field type="text" autoComplete="off" name="icon" placeholder="Icon">
{({ field, form }: FieldProps<string>) => (
<Select
field={field}
form={form}
isClearable={true}
componentAs={CreatableSelect}
placeholder="Icon"
options={commandButtonIconOptions}
value={
values.icon
? (commandButtonIconOptions.find(
(option) => option.value === values.icon,
) ?? { value: values.icon, label: values.icon })
: null
}
formatOptionLabel={(option: SelectBoxOption) => (
<span className="flex items-center gap-2">
<i className={`dx-icon dx-icon-${option.value}`} />
{option.label}
</span>
)}
onChange={(option: SelectBoxOption | null) =>
form.setFieldValue(field.name, option?.value ?? '')
}
/>
)}
</Field>
</FormItem>
<FormItem
label="Is Visible"

View file

@ -27,6 +27,7 @@ import {
} from '../../../../proxy/admin/list-form-field/models'
import { enumToList } from '../../../../utils/enumUtils'
import { PREFIX } from '@/constants/theme.constant'
import { SelectBoxOption } from '@/types/shared'
export enum WidgetColorEnum {
'Blue' = 'blue',
@ -696,3 +697,112 @@ export const styleMapOptions = enumToList<string>(StyleEnum).map((opt) => ({
...opt,
color: styleColorMap[opt.value],
}))
/**
* Grid/Toolbar komut butonlarının ikon seçenekleri. Değerler DevExtreme ikon
* adlarıdır (`dx-icon-<ad>`); butonlar ikonu doğrudan bu adla çizer.
*/
export const commandButtonIconOptions: SelectBoxOption[] = [
'add',
'plus',
'minus',
'remove',
'trash',
'edit',
'save',
'undo',
'revert',
'refresh',
'clear',
'close',
'check',
'apply',
'checklist',
'copy',
'paste',
'cut',
'search',
'find',
'filter',
'sorted',
'columnchooser',
'fields',
'preferences',
'card',
'detailslayout',
'contentlayout',
'menu',
'more',
'overflow',
'export',
'exportpdf',
'exportxlsx',
'exportselected',
'import',
'upload',
'download',
'doc',
'docfile',
'docxfile',
'pdffile',
'xlsfile',
'xlsxfile',
'csv',
'file',
'folder',
'print',
'email',
'message',
'comment',
'bell',
'share',
'link',
'globe',
'home',
'user',
'group',
'key',
'lock',
'unlock',
'verticalaligntop',
'chart',
'datapie',
'datausage',
'money',
'cart',
'product',
'box',
'photo',
'image',
'video',
'attach',
'clock',
'event',
'todo',
'runner',
'repeat',
'info',
'help',
'warning',
'errorcircle',
'checkmarkcircle',
'favorites',
'like',
'pin',
'map',
'tel',
'back',
'chevronleft',
'chevronright',
'chevrondown',
'chevronup',
'arrowleft',
'arrowright',
'arrowup',
'arrowdown',
'expand',
'collapse',
'fullscreen',
'hierarchy',
'floppy',
].map((icon) => ({ value: icon, label: icon }))

View file

@ -1,21 +1,13 @@
import { Badge, Button, Dialog, Notification, toast } from '@/components/ui'
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
import { Button, Dialog, Notification, toast } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import CustomStore from 'devextreme/data/custom_store'
import Toolbar from 'devextreme-react/toolbar'
import type { dxToolbarItem as DxToolbarItem } from 'devextreme/ui/toolbar'
import { useState } from 'react'
import {
FaChevronLeft,
FaTrashAlt,
FaPlusCircle,
FaPencilAlt,
FaEye,
FaSave,
FaCog,
FaSistrix,
} from 'react-icons/fa'
import { useNavigate } from 'react-router-dom'
import { GridColumnData } from '../list/GridColumnData'
import { isWorkflowToolbarItemDisabled, useToolbar } from '../list/useToolbar'
import { orderListToolbarItems } from '../list/toolbarOrder'
import { PermissionResults, RowMode } from './types'
import { GridDto } from '@/proxy/form/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -162,194 +154,178 @@ const FormButtons = (props: {
}
}
const navigationIcon = useNavigationIcons()
const toolbarExcludedNames = ['deleteAllRecords', 'deleteSelectedRecords', 'refreshButton']
const hasVisibleToolbarButtons = toolbarData?.some(isToolbarButtonVisible)
const commandExcludedNames = ['edit', 'delete', 'detail']
const hasVisibleCommandButtons = commandColumnData?.buttons?.some(isCommandButtonVisible)
/** Grid toolbar sozlesmesiyle ayni bicimde bir dxButton ogesi uretir. */
const buildButtonItem = (
icon: string,
text: string,
onClick: (e: any) => void,
disabled = false,
): DxToolbarItem => ({
location: 'after',
widget: 'dxButton',
locateInMenu: 'auto',
showText: 'always',
options: { icon, text, hint: text, disabled, onClick },
})
const toolbarItems: DxToolbarItem[] = [
// Liste toolbar'indan gelen ozel butonlar; grid ile ayni sirayi korur.
...orderListToolbarItems(toolbarData?.filter(isToolbarButtonVisible) ?? []).map(
(item): DxToolbarItem => ({
location: 'after',
widget: 'dxButton',
locateInMenu: 'auto',
showText: 'always',
options: {
...item.options,
// Gorunurluk zaten filtrelendi; DX'e fonksiyon gecilmemeli.
visible: true,
disabled: isToolbarButtonDisabled(item),
onClick: (e: any) => item.options?.onClick?.({ ...e, ...rowEvent }),
},
}),
),
// Komut kolonundan toolbar'a tasinan butonlar.
...(commandColumnData?.buttons?.filter(isCommandButtonVisible) ?? []).map(
(item: any): DxToolbarItem => ({
location: 'after',
widget: 'dxButton',
locateInMenu: 'auto',
showText: 'always',
options: {
icon: item.icon,
text: item.text,
hint: item.hint ?? item.text,
onClick: (e: any) => item.onClick?.({ ...e, ...rowEvent }),
},
}),
),
]
if (!isSubForm) {
toolbarItems.push(
buildButtonItem('back', translate('::App.Platform.Cancel'), () => {
if (onActionView && id) {
onActionView()
} else {
navigate(-1)
}
}),
)
}
if (mode !== 'new') {
toolbarItems.push(
buildButtonItem(
'trash',
translate('::App.Platform.Delete'),
() => setDeleteRowId(id),
!permissions.d,
),
buildButtonItem(
'add',
translate('::App.Platform.AddNew'),
() => {
if (onActionNew) {
onActionNew()
} else {
navigate(
ROUTES_ENUM.protected.admin.formNew.replace(
':listFormCode',
encodeURIComponent(listFormCode),
),
)
}
},
!permissions.c,
),
)
}
if (mode === 'view') {
toolbarItems.push(
buildButtonItem(
'edit',
translate('::App.Platform.Edit'),
() => {
if (onActionEdit) {
onActionEdit()
} else {
navigate(
ROUTES_ENUM.protected.admin.formEdit
.replace(':listFormCode', encodeURIComponent(listFormCode))
.replace(':id', encodeURIComponent(id!)),
)
}
},
!permissions.u,
),
)
}
if (mode === 'edit' || (onActionView && mode === 'new')) {
toolbarItems.push(
buildButtonItem(
'eyeopen',
translate('::App.Platform.Detail'),
() => {
if (onActionView) {
onActionView()
} else {
navigate(
ROUTES_ENUM.protected.admin.formView
.replace(':listFormCode', encodeURIComponent(listFormCode))
.replace(':id', encodeURIComponent(id!)),
)
}
},
!permissions.r,
),
)
}
if (mode === 'edit' || mode === 'new') {
toolbarItems.push(
buildButtonItem(
'save',
translate('::App.Platform.Save'),
handleSubmit,
!(permissions.c || permissions.u),
),
)
}
if (id && !isSubForm) {
toolbarItems.push(
buildButtonItem(
'doc',
translate('::App.Reports.Form'),
openDynamicGridReport,
),
)
}
if (checkPermission('App.Listforms.Listform.Update') && !isSubForm) {
toolbarItems.push(
buildButtonItem('preferences', translate('::App.Platform.Manage'), () => {
window.open(
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(
':listFormCode',
listFormCode,
),
isPwaMode ? '_self' : '_blank',
)
}),
)
}
return (
<>
<div className="flex flex-row items-center gap-1">
{toolbarData
?.filter(isToolbarButtonVisible)
.map((item, i) => {
const IconComp = navigationIcon[item.options?.icon]
const hasValidIcon =
IconComp &&
(typeof IconComp === 'function' ||
(typeof IconComp === 'object' && 'render' in IconComp))
return (
<Button
key={'toolbarButton-' + i}
variant="default"
size="sm"
icon={hasValidIcon ? <IconComp className="text-gray-400" /> : null}
disabled={isToolbarButtonDisabled(item)}
onClick={(e) => item.options?.onClick?.({ ...e, ...rowEvent })}
>
{item.options?.text}
</Button>
)
})}
{hasVisibleToolbarButtons && <Badge innerClass="bg-blue-500" />}
{commandColumnData?.buttons
?.filter(isCommandButtonVisible)
.map((item: any, i) => {
return (
<Button
key={'commandColumnButton-' + i}
variant="default"
size="sm"
title={item.hint}
onClick={(e: any) => {
if (item.onClick) {
item.onClick({ ...e, ...rowEvent })
}
}}
>
{item.text}
</Button>
)
})}
{hasVisibleCommandButtons && <Badge innerClass="bg-blue-500" />}
{!isSubForm && (
<Button
size="sm"
variant="default"
icon={<FaChevronLeft />}
title={translate('::App.Platform.Cancel')}
onClick={() => {
if (onActionView && id) {
onActionView()
} else {
navigate(-1)
}
}}
></Button>
)}
{mode != 'new' && (
<Button
size="sm"
variant="solid"
icon={<FaTrashAlt />}
title={translate('::App.Platform.Delete')}
onClick={() => {
setDeleteRowId(id)
}}
{...(permissions.d ? {} : { disabled: true })}
></Button>
)}
{mode != 'new' && (
<Button
size="sm"
variant="solid"
color='blue-500'
icon={<FaPlusCircle />}
title={translate('::App.Platform.AddNew')}
onClick={() => {
if (onActionNew) {
onActionNew()
} else {
navigate(
ROUTES_ENUM.protected.admin.formNew.replace(
':listFormCode',
encodeURIComponent(listFormCode),
),
)
}
}}
{...(permissions.c ? {} : { disabled: true })}
></Button>
)}
{mode == 'view' && (
<Button
size="sm"
variant="solid"
color="green-600"
icon={<FaPencilAlt />}
title={translate('::App.Platform.Edit')}
onClick={() => {
if (onActionEdit) {
onActionEdit()
} else {
navigate(
ROUTES_ENUM.protected.admin.formEdit
.replace(':listFormCode', encodeURIComponent(listFormCode))
.replace(':id', encodeURIComponent(id!)),
)
}
}}
{...(permissions.u ? {} : { disabled: true })}
></Button>
)}
{(mode == 'edit' || (onActionView && mode == 'new')) && (
<Button
size="sm"
variant="solid"
color="gray-500"
icon={<FaEye />}
title={translate('::App.Platform.Detail')}
onClick={() => {
if (onActionView) {
onActionView()
} else {
navigate(
ROUTES_ENUM.protected.admin.formView
.replace(':listFormCode', encodeURIComponent(listFormCode))
.replace(':id', encodeURIComponent(id!)),
)
}
}}
{...(permissions.r ? {} : { disabled: true })}
></Button>
)}
{(mode == 'edit' || mode == 'new') && (
<Button
size="sm"
variant="solid"
icon={<FaSave />}
title={translate('::App.Platform.Save')}
onClick={handleSubmit}
{...(permissions.c || permissions.u ? {} : { disabled: true })}
></Button>
)}
{id && !isSubForm && (
<Button
size="sm"
variant="solid"
color="purple-500"
icon={<FaSistrix />}
title={translate('::App.Reports.Form')}
onClick={openDynamicGridReport}
></Button>
)}
{checkPermission('App.Listforms.Listform.Update') && !isSubForm && (
<Button
size="sm"
variant="solid"
icon={<FaCog />}
color="green-500"
title={translate('::App.Platform.Manage')}
onClick={() => {
window.open(
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(
':listFormCode',
listFormCode,
),
isPwaMode ? '_self' : '_blank',
)
}}
></Button>
)}
</div>
<Toolbar className="list-view-toolbar-host flex-1" items={toolbarItems} />
<Dialog
isOpen={toolbarModalData?.open || false}
onClose={() => setToolbarModalData(undefined)}

View file

@ -139,7 +139,7 @@ const Blog: React.FC = () => {
value={searchQuery}
placeholder={translateLabel(translate, 'App.PublicBlog.SearchPlaceholder')}
aria-label={translate('::App.Platform.Search3')}
className="w-full rounded-lg border border-gray-300 bg-white py-2 pl-10 text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400"
className="rounded-lg border border-gray-300 bg-white py-2 pl-10 text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400"
onChange={(event) => setSearchQuery(event.target.value)}
/>
<FaSearch className="absolute left-3 top-2.5 h-5 w-5 text-gray-400" />