diff options
| author | Valentin Popov <valentin@popov.link> | 2026-02-05 14:53:17 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-02-05 14:53:17 +0300 |
| commit | 7fe5502dc173cfee7c3b3178fb233264ad7c6dc3 (patch) | |
| tree | e8d82ba993ae0a044c3ce322114851aa9683b51c /admin/src | |
| parent | efa89313fa4152252b477aafd88f7cf4a66747d8 (diff) | |
| download | strapi-plugin-checkbox-list-7fe5502dc173cfee7c3b3178fb233264ad7c6dc3.tar.xz strapi-plugin-checkbox-list-7fe5502dc173cfee7c3b3178fb233264ad7c6dc3.zip | |
Add checkbox-list custom field plugin to Strapi
- Introduced a new custom field type 'checkbox-list' with associated input component.
- Updated package.json to reflect the new plugin name.
- Added necessary server-side files for plugin registration, including bootstrap, destroy, and service methods.
- Updated package-lock.json to include new dependencies and versions.
- Enhanced admin interface with custom field registration and input handling.
Diffstat (limited to 'admin/src')
| -rw-r--r-- | admin/src/components/CheckboxListInput.tsx | 115 | ||||
| -rw-r--r-- | admin/src/index.ts | 52 |
2 files changed, 165 insertions, 2 deletions
diff --git a/admin/src/components/CheckboxListInput.tsx b/admin/src/components/CheckboxListInput.tsx new file mode 100644 index 0000000..6da9766 --- /dev/null +++ b/admin/src/components/CheckboxListInput.tsx @@ -0,0 +1,115 @@ +import type { ReactNode } from 'react'; + +import { Box, Checkbox, Field, Flex, Typography } from '@strapi/design-system'; +import { useIntl } from 'react-intl'; + +type CheckboxListInputProps = { + name: string; + value?: unknown; + onChange: (eventOrPath: { target: { name: string; value: string[] } }, value?: unknown) => void; + attribute?: { + enum?: string[]; + options?: { + enum?: string[]; + }; + } | null; + label?: ReactNode; + hint?: ReactNode; + required?: boolean; + disabled?: boolean; + error?: string; + labelAction?: ReactNode; +}; + +const getEnumValues = (attribute: CheckboxListInputProps['attribute']): string[] => { + if (!attribute) { + return []; + } + + if (Array.isArray(attribute.enum)) { + return attribute.enum; + } + + if (Array.isArray(attribute.options?.enum)) { + return attribute.options.enum; + } + + return []; +}; + +const normalizeValue = (value: unknown): string[] => { + if (Array.isArray(value)) { + return value.filter((item): item is string => typeof item === 'string'); + } + + if (typeof value === 'string' && value.length > 0) { + return [value]; + } + + return []; +}; + +const CheckboxListInput = ({ + name, + value, + onChange, + attribute, + label, + hint, + required = false, + disabled = false, + error, + labelAction, +}: CheckboxListInputProps) => { + const { formatMessage } = useIntl(); + const enumValues = getEnumValues(attribute); + const selectedValues = normalizeValue(value); + + const handleToggle = (option: string, isChecked: boolean) => { + const nextValues = isChecked + ? Array.from(new Set([...selectedValues, option])) + : selectedValues.filter((item) => item !== option); + + onChange({ + target: { + name, + value: nextValues, + }, + }); + }; + + return ( + <Field.Root name={name} hint={hint} error={error} required={required}> + <Field.Label action={labelAction}>{label ?? name}</Field.Label> + {enumValues.length > 0 ? ( + <Flex direction="column" gap={2} paddingTop={1}> + {enumValues.map((option) => ( + <Checkbox + key={option} + checked={selectedValues.includes(option)} + disabled={disabled} + onCheckedChange={(checked: boolean | 'indeterminate') => + handleToggle(option, Boolean(checked)) + } + > + {option} + </Checkbox> + ))} + </Flex> + ) : ( + <Box paddingTop={1}> + <Typography variant="pi" textColor="neutral500"> + {formatMessage({ + id: 'checkbox-list.field.empty', + defaultMessage: 'No values configured yet.', + })} + </Typography> + </Box> + )} + <Field.Error /> + <Field.Hint /> + </Field.Root> + ); +}; + +export default CheckboxListInput; diff --git a/admin/src/index.ts b/admin/src/index.ts index 4dff817..f3e7a28 100644 --- a/admin/src/index.ts +++ b/admin/src/index.ts @@ -1,7 +1,8 @@ -import { getTranslation } from './utils/getTranslation'; -import { PLUGIN_ID } from './pluginId'; +import { Check } from '@strapi/icons'; import { Initializer } from './components/Initializer'; import { PluginIcon } from './components/PluginIcon'; +import { PLUGIN_ID } from './pluginId'; +import { getTranslation } from './utils/getTranslation'; export default { register(app: any) { @@ -25,6 +26,53 @@ export default { isReady: false, name: PLUGIN_ID, }); + + app.customFields.register({ + name: 'checkbox-list', + pluginId: PLUGIN_ID, + type: 'json', + icon: Check, + intlLabel: { + id: `${PLUGIN_ID}.customField.label`, + defaultMessage: 'Checkbox list', + }, + intlDescription: { + id: `${PLUGIN_ID}.customField.description`, + defaultMessage: 'Select multiple values from a list', + }, + components: { + Input: async () => { + const { default: Component } = await import('./components/CheckboxListInput'); + + return { default: Component }; + }, + }, + options: { + base: [ + { + sectionTitle: null, + items: [ + { + name: 'enum', + type: 'textarea-enum', + size: 6, + intlLabel: { + id: 'form.attribute.item.enumeration.rules', + defaultMessage: 'Values (one line per value)', + }, + placeholder: { + id: 'form.attribute.item.enumeration.placeholder', + defaultMessage: 'Ex:\nmorning\nnoon\nevening', + }, + validations: { + required: true, + }, + }, + ], + }, + ], + }, + }); }, async registerTrads({ locales }: { locales: string[] }) { |
