Skip to content

Commit 5e94500

Browse files
ivictborCopilot
andcommitted
feat(docs): Enhance documentation with detailed descriptions for various tutorials and plugins
- Added descriptions to the "Getting Started", "Hello World", "Glossary", and various customization tutorials to improve clarity and guidance. - Updated plugin documentation with comprehensive descriptions for each plugin, including setup instructions and usage scenarios. - Improved the overall structure and readability of the documentation by ensuring consistent formatting and adding necessary metadata. - Introduced a new Docusaurus plugin for better markdown handling and enhanced CLI command references. - Deprecated the bulk action request in the Common types, advising against its use in favor of custom actions. Co-authored-by: Copilot <copilot@github.com>
1 parent 97aab4c commit 5e94500

72 files changed

Lines changed: 852 additions & 69 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
---
2+
name: adminforth-custom-vue
3+
description: "Use when implementing AdminForth custom Vue UI: field components, page injections, login or global injections, meta-driven component declarations, and frontend packages inside custom/."
4+
user-invocable: true
5+
---
6+
7+
# AdminForth Custom Vue Workflow
8+
9+
## When to Use
10+
11+
- Editing files under `custom/`.
12+
- Adding custom field renderers or editors via `column.components`.
13+
- Adding resource page injections, login injections, or global layout injections.
14+
- Passing `meta` into reusable Vue components.
15+
- Installing frontend packages used only by custom AdminForth Vue code.
16+
17+
## `custom/` Directory and `@@/`
18+
19+
- `custom/` is the frontend workspace for AdminForth custom Vue code.
20+
- By default, `@@/Something.vue` means “resolve this path from `customComponentsDir` on the backend”, and `customComponentsDir` defaults to `./custom`.
21+
- Example: `@@/reports/OrdersHeader.vue` maps to `./custom/reports/OrdersHeader.vue`.
22+
- The same `@@/` prefix also works for assets and helper files that are bundled into the SPA.
23+
24+
## Frontend Packages in `custom/`
25+
26+
- Install frontend-only dependencies inside `custom/`, not in the app root.
27+
28+
```bash
29+
cd custom
30+
{{packageManager}} install apexcharts vue3-apexcharts
31+
```
32+
33+
- If `custom/package.json` does not exist yet, initialize the folder first.
34+
35+
```bash
36+
cd custom
37+
{{packageManager}} init
38+
```
39+
40+
## Organizing Components
41+
42+
- You can freely create subfolders and split components into smaller children.
43+
- Example structure:
44+
45+
```text
46+
custom/
47+
reports/
48+
OrdersTopPanel.vue
49+
parts/
50+
OrdersTotals.vue
51+
OrdersFilters.vue
52+
```
53+
54+
- Example parent component using subcomponents:
55+
56+
```vue
57+
<template>
58+
<section class="grid gap-4 md:grid-cols-2">
59+
<OrdersTotals :record="record" />
60+
<OrdersFilters :meta="meta" />
61+
</section>
62+
</template>
63+
64+
<script setup lang="ts">
65+
import OrdersTotals from './parts/OrdersTotals.vue';
66+
import OrdersFilters from './parts/OrdersFilters.vue';
67+
68+
defineProps<{
69+
record?: any;
70+
meta?: any;
71+
}>();
72+
</script>
73+
```
74+
75+
- In AdminForth config you still reference only the entry component:
76+
77+
```ts
78+
beforeBreadcrumbs: '@@/reports/OrdersTopPanel.vue'
79+
```
80+
81+
## Short vs Full Component Declaration
82+
83+
- Short declaration is just a file string:
84+
85+
```ts
86+
show: '@@/RoomsCell.vue'
87+
```
88+
89+
- Full declaration lets you pass `meta`:
90+
91+
```ts
92+
show: {
93+
file: '@@/RoomsCell.vue',
94+
meta: {
95+
filler: '🟨',
96+
},
97+
}
98+
```
99+
100+
- Use full declaration when the same component should behave differently in different places.
101+
- `meta` is passed into the Vue component as a prop.
102+
- Common `meta` use cases are reusable display options, plugin settings, `thinEnoughToShrinkTable`, `afOrder`, or custom page layout flags.
103+
104+
## Field Component Spots
105+
106+
- `components.show`: custom value renderer on show page.
107+
- `components.showRow`: replaces the full show-table row.
108+
- `components.create`: custom editor on create page.
109+
- `components.edit`: custom editor on edit page.
110+
- `components.list`: custom value renderer in list cells.
111+
- `components.filter`: custom filter input.
112+
113+
## Field Component Props and Emits
114+
115+
- `show` and `list` components should expect `column`, `record`, `resource`, `adminUser`, and optional `meta`.
116+
- In practice, custom `edit` and `create` components receive `column`, `value`, `record`, `resource`, `adminUser`, `readonly`, and optional `meta`.
117+
- Custom `edit` and `create` components can emit:
118+
- `update:value` to change the current field value.
119+
- `update:recordFieldValue` to change another field in the same record.
120+
- `update:inValidity` to report custom validation state.
121+
- `update:emptiness` to report custom emptiness rules.
122+
- If you update hidden technical fields through `update:recordFieldValue`, the target column may need `allowModifyWhenNotShowInCreate` or `allowModifyWhenNotShowInEdit`.
123+
124+
## Custom Edit Component Example
125+
126+
```vue
127+
<template>
128+
<div class="grid gap-2">
129+
<Input
130+
:model-value="localValue"
131+
:readonly="readonly"
132+
:placeholder="meta?.placeholder || column.label"
133+
@update:model-value="onInput"
134+
/>
135+
136+
<p v-if="errorMessage" class="text-sm text-red-600">
137+
{{ errorMessage }}
138+
</p>
139+
140+
<p v-else-if="isEmpty" class="text-sm text-amber-600">
141+
Value is currently empty
142+
</p>
143+
</div>
144+
</template>
145+
146+
<script setup lang="ts">
147+
import { computed, onMounted, ref } from 'vue';
148+
import Input from '@/afcl/Input.vue';
149+
import type {
150+
AdminForthResourceColumnCommon,
151+
AdminForthResourceCommon,
152+
AdminUser,
153+
} from '@/types/Common';
154+
155+
const props = defineProps<{
156+
column: AdminForthResourceColumnCommon;
157+
value: string | null | undefined;
158+
record: Record<string, any>;
159+
meta?: { minLength?: number; placeholder?: string };
160+
resource: AdminForthResourceCommon;
161+
adminUser: AdminUser;
162+
readonly: boolean;
163+
}>();
164+
165+
const emit = defineEmits<{
166+
(e: 'update:value', value: string): void;
167+
(e: 'update:recordFieldValue', payload: { fieldName: string; fieldValue: any }): void;
168+
(e: 'update:inValidity', value: string | false): void;
169+
(e: 'update:emptiness', value: boolean): void;
170+
}>();
171+
172+
const localValue = ref(props.value ?? '');
173+
const WHITESPACE_RE = /\s+/g;
174+
175+
const isEmpty = computed(() => localValue.value.trim() === '');
176+
const errorMessage = computed(() => {
177+
const minLength = props.meta?.minLength ?? 3;
178+
if (isEmpty.value) {
179+
return false;
180+
}
181+
return localValue.value.trim().length < minLength
182+
? `Minimum length is ${minLength}`
183+
: false;
184+
});
185+
186+
onMounted(syncState);
187+
188+
function onInput(nextValue: string) {
189+
localValue.value = nextValue;
190+
emit('update:value', nextValue);
191+
emit('update:recordFieldValue', {
192+
fieldName: 'slug',
193+
fieldValue: nextValue.toLowerCase().trim().replace(WHITESPACE_RE, '-'),
194+
});
195+
syncState();
196+
}
197+
198+
function syncState() {
199+
// for edit/create components where emptiness is not obvious (e.g. image was not uploaded), always emit emptiness when it change
200+
emit('update:emptiness', isEmpty.value);
201+
202+
// for custom validity (e.g. upload still in progress), emit error message or false for valid
203+
emit('update:inValidity', errorMessage.value || false);
204+
}
205+
</script>
206+
```
207+
208+
## Custom List and Show Example with `meta`
209+
210+
```ts
211+
{
212+
name: 'number_of_rooms',
213+
components: {
214+
show: {
215+
file: '@@/RoomsCell.vue',
216+
meta: { filler: '🟨', suffix: 'rooms' },
217+
},
218+
list: {
219+
file: '@@/RoomsCell.vue',
220+
meta: { filler: '🟦', suffix: 'r' },
221+
},
222+
},
223+
}
224+
```
225+
226+
```vue
227+
<template>
228+
<div class="flex items-center gap-2">
229+
<span>
230+
{{ meta?.filler?.repeat(record.number_of_rooms || 0) }}
231+
</span>
232+
<span>{{ record.number_of_rooms }} {{ meta?.suffix }}</span>
233+
</div>
234+
</template>
235+
236+
<script setup lang="ts">
237+
import type {
238+
AdminForthResourceColumnCommon,
239+
AdminForthResourceCommon,
240+
AdminUser,
241+
} from '@/types/Common';
242+
243+
defineProps<{
244+
column: AdminForthResourceColumnCommon;
245+
record: Record<string, any>;
246+
meta?: { filler?: string; suffix?: string };
247+
resource: AdminForthResourceCommon;
248+
adminUser: AdminUser;
249+
}>();
250+
</script>
251+
```
252+
253+
## Resource Page Injection Spots
254+
255+
- `pageInjections.list.beforeBreadcrumbs`
256+
- `pageInjections.list.afterBreadcrumbs`
257+
- `pageInjections.list.beforeActionButtons`
258+
- `pageInjections.list.bottom`
259+
- `pageInjections.list.threeDotsDropdownItems`
260+
- `pageInjections.list.customActionIcons`
261+
- `pageInjections.list.customActionIconsThreeDotsMenuItems`
262+
- `pageInjections.list.tableBodyStart`
263+
- `pageInjections.list.tableRowReplace`
264+
- `pageInjections.show.beforeBreadcrumbs`
265+
- `pageInjections.show.afterBreadcrumbs`
266+
- `pageInjections.show.bottom`
267+
- `pageInjections.show.threeDotsDropdownItems`
268+
- `pageInjections.edit.beforeBreadcrumbs`
269+
- `pageInjections.edit.afterBreadcrumbs`
270+
- `pageInjections.edit.bottom`
271+
- `pageInjections.edit.threeDotsDropdownItems`
272+
- `pageInjections.create.beforeBreadcrumbs`
273+
- `pageInjections.create.afterBreadcrumbs`
274+
- `pageInjections.create.bottom`
275+
- `pageInjections.create.threeDotsDropdownItems`
276+
277+
## Login and Global Injection Spots
278+
279+
- `customization.loginPageInjections.panelHeader`
280+
- `customization.loginPageInjections.underInputs`
281+
- `customization.loginPageInjections.underLoginButton`
282+
- `customization.globalInjections.userMenu`
283+
- `customization.globalInjections.header`
284+
- `customization.globalInjections.sidebar`
285+
- `customization.globalInjections.sidebarTop`
286+
- `customization.globalInjections.everyPageBottom`
287+
288+
## Top Injection Example
289+
290+
```ts
291+
options: {
292+
pageInjections: {
293+
list: {
294+
beforeBreadcrumbs: {
295+
file: '@@/reports/OrdersTopPanel.vue',
296+
meta: {
297+
title: 'Orders overview',
298+
},
299+
},
300+
},
301+
},
302+
}
303+
```
304+
305+
## List Injection Shrink Behavior
306+
307+
- On list pages, AdminForth tries to keep the table itself scrollable when there are no large top or bottom injections.
308+
- That shrink behavior is affected by four list injection spots: `beforeBreadcrumbs`, `afterBreadcrumbs`, `beforeActionButtons`, and `bottom`.
309+
- If none of those four spots are used, the table tries to shrink into the viewport and vertical scrolling happens inside the table area.
310+
- If any of those spots are used, AdminForth assumes the page may now need extra vertical space, so the table stops shrinking and page scrolling moves to the document body instead.
311+
- If your injected panel is actually compact, set `meta.thinEnoughToShrinkTable: true` on that injection.
312+
- Important: every injection in those four spots must set `thinEnoughToShrinkTable: true`, otherwise the table will still stay in non-shrinking mode.
313+
314+
```ts
315+
options: {
316+
pageInjections: {
317+
list: {
318+
bottom: {
319+
file: '@@/reports/OrdersSummary.vue',
320+
meta: {
321+
thinEnoughToShrinkTable: true,
322+
},
323+
},
324+
},
325+
},
326+
}
327+
```
328+
329+
## Row-Level List Injection Notes
330+
331+
- `tableBodyStart` renders extra `<tr>` rows near the start of the table body and receives `resource`, `adminUser`, and `meta`.
332+
- `tableRowReplace` replaces the default `<tr>` for each record and receives `record`, `resource`, `adminUser`, and `meta`.
333+
- `customActionIcons` and `customActionIconsThreeDotsMenuItems` are per-record spots and receive `record`, `resource`, `adminUser`, `meta`, and `updateRecords`.
334+
335+
## Practical Rules
336+
337+
- Prefer simple string declarations until you actually need `meta`.
338+
- Reuse one component with multiple full declarations instead of cloning similar files.
339+
- Keep page injections small unless the layout intentionally becomes page-scrolling.
340+
- Keep custom edit and create components explicit about validity and emptiness if the default input heuristics are not enough.

0 commit comments

Comments
 (0)