Roles

Search and pick roles for an Auto Form field - one, or as many as you like.

Preview

One role, replaced each time you pick.

Pick as many as you like. Picking one twice removes it.

Usage

import { z } from 'zod'
import { AutoForm } from '@vitnode/core/components/form/auto-form'
import { AutoFormRoles } from '@vitnode/core/components/form/fields/input-roles'

One component covers both shapes, because the difference is the value and nothing else - the search, the colour, the language resolution and the empty state are identical, and two copies is how they drift.

const formSchema = z.object({
  roleId: z.number(),
})
<AutoForm
  formSchema={formSchema}
  fields={[
    {
      id: 'roleId',
      component: (props) => (
        <AutoFormRoles
          {...props}
          description="One role, replaced each time you pick."
          label="Primary role"
          placeholder="Select a role"
        />
      ),
    },
  ]}
/>

The value is a single id, and picking again replaces it.

const formSchema = z.object({
  roleIds: z.array(z.number()).min(1),
})
<AutoForm
  formSchema={formSchema}
  fields={[
    {
      id: 'roleIds',
      component: (props) => (
        <AutoFormRoles
          {...props}
          label="Additional roles"
          multiple
          placeholder="Add a role"
        />
      ),
    },
  ]}
/>

The value is an array of ids. Chosen roles appear above the picker as removable chips, and the picker appends rather than replaces - picking one that is already chosen removes it, which is what the tick beside it in the list means.

Out of the box it searches the AdminCP roles list. The guest role is never offered: it is the role a request has when it has no account, so it is not something to assign to anybody.

Editing an existing record

Same rule as User: the picker can only name ids it has seen, so an edit form passes the roles it already knows about.

<AutoFormRoles {...props} multiple label="Roles" selected={user.roles} />

Keeping a role out of the list

excludeIds drops options another field already owns - a primary-role picker and a secondary-role picker should not both offer the same one:

<AutoFormRoles
  {...props}
  multiple
  excludeIds={[primaryRoleId]}
  label="Additional roles"
/>

Names are resolved per reader

A role carries one name per language. The field renders the active locale's, and falls back to the first translation rather than to the id - a role with no translation in your language is still a role somebody named:

import { roleOptionName } from '@vitnode/core/components/form/fields/input-roles'

roleOptionName(role, 'pl') // "Administrator PL", or the first name it has

Props

PropTypeDefaultWhat it does
multiplebooleanfalsenumber[] instead of number, with chips
labelReactNode-Field label
descriptionReactNode-Help text under the control
placeholderstringSelect an optionShown while nothing is chosen
searchPlaceholderstringSearch...Placeholder inside the search box
selectedRoleOption[][]Roles the field opens on
excludeIdsnumber[][]Roles the picker must not offer
search(value: string) => Promise<RoleOption[]>AdminCP roles listReplaces the lookup
disabledbooleanfalseBlocks opening the picker and removing chips

RoleOption is { id, color, name }, where name is the raw { languageCode, name }[] - the server has no business deciding which language the person clicking reads in.

See also

  • User - the same idea for people.
  • Combobox - when the options are strings rather than records.