AccessRightSelect
Drop-down for choosing an access level, with an icon, a description and a paid badge on each
row. It is a ComboBox whose options are laid out as access rows.
Use this when / not when
- Use wherever a role or permission is picked — inviting someone to a room, changing a member's access, setting what a share link allows.
- Not for a plain list of options.
ComboBoxis that; this one spends its layout on the icon, the second line and the badge. - Not for choices that are not exclusive, and not for more than a screenful — the drop-down does not paginate.
- Not to show an access level that cannot be changed. Render the label as text instead of a disabled control.
Import
import { AccessRightSelect } from "@onlyoffice/apps-ui-kit/components/access-right-select";
Also exported from the root barrel @onlyoffice/apps-ui-kit.
AccessRightSelectProps is not exported — type a wrapper's props yourself.
Needs ThemeProvider from @onlyoffice/apps-ui-kit/providers/theme, and TranslationProvider
from @onlyoffice/apps-ui-kit/providers/translation because a refused choice is reported
through a toast. Mount Toast once in your app, or that message is lost.
Stories
Default
The drop-down as it is placed next to a person or a link: open it to see each level's icon, description and paid badge, and pick one to see the button follow (onSelect, logged in the Actions panel). Change any other prop live in the Controls panel below.
Display Types
How much of the chosen level the button shows, from the most room to the least:
- Editor — the label alone, the usual form (no
type) - Editor, Can edit and share files — the label with the description underneath, for a form where the choice needs explaining (
type="descriptive") - The folder icon — the icon alone, for a row with no room for text; the list still shows every label (
type="onlyIcon")
Restricted Choices
For a level the viewer may see but not grant. Open the list and pick Full access: a toast explains why, and the button keeps Viewer; Commenter and Viewer can still be picked (isSelectionDisabled, availableAccess, selectionErrorText). The toast needs Toast mounted once in the app.
Disabled State
For an access level that cannot be changed right now, such as while the person is being removed: the button is greyed out and clicking it does not open the list (isDisabled).
Loading State
For the moment a new level is being saved: a spinner takes the place of the label and icon, and the list cannot be opened until the save finishes (isLoading).
Css Customization
Every overridable variable set on one wrapper -- the variables are listed under CSS variables on this page. Open any of the instances to see the row and panel variables; they render the list in place (isDefaultMode={false}), because a portalled list is out of reach of a wrapper's variables. The three instances share one wrapper:
- Full access — the usual button, for the button radius and everything in the list
- The first icon —
type="onlyIcon", for--access-right-select-text - The second icon —
type="onlyIcon"andisDisabled, for--access-right-select-disabled-icon
Minimal example
Each option is a TOption: key identifies it, label is the first line, description the
second, and access is the value you care about.
import { useState } from "react";
import { AccessRightSelect } from "@onlyoffice/apps-ui-kit/components/access-right-select";
import type { TOption } from "@onlyoffice/apps-ui-kit/components/combobox";
const OPTIONS: TOption[] = [
{
key: "editor",
label: "Editor",
description: "Can edit and share",
access: 1,
},
{ key: "viewer", label: "Viewer", description: "Can only read", access: 2 },
];
export function MemberAccess() {
const [access, setAccess] = useState<TOption>(OPTIONS[0]);
return (
<AccessRightSelect
accessOptions={OPTIONS}
selectedOption={access}
scaled={false}
onSelect={(option) => setAccess(option)}
/>
);
}
Props
Inherited from TComboboxProps
Declared by components/combobox and accepted here too.
Recipes
Loading
isLoading is passed through to the combo button, which shows the kit's loader in place of the
label.
import { useState } from "react";
import { AccessRightSelect } from "@onlyoffice/apps-ui-kit/components/access-right-select";
import type { TOption } from "@onlyoffice/apps-ui-kit/components/combobox";
const OPTIONS: TOption[] = [
{ key: "editor", label: "Editor", access: 1 },
{ key: "viewer", label: "Viewer", access: 2 },
];
export function SavingAccess({
save,
}: {
save: (option: TOption) => Promise<void>;
}) {
const [access, setAccess] = useState<TOption>(OPTIONS[0]);
const [isSaving, setIsSaving] = useState(false);
return (
<AccessRightSelect
accessOptions={OPTIONS}
selectedOption={access}
isLoading={isSaving}
onSelect={async (option) => {
setIsSaving(true);
await save(option);
setAccess(option);
setIsSaving(false);
}}
/>
);
}
Refusing some levels
isSelectionDisabled turns the list into a set of levels that may be seen but mostly not
chosen. availableAccess names the ones that still work; anything else raises
selectionErrorText as a toast and the selection does not move.
import { useState } from "react";
import { AccessRightSelect } from "@onlyoffice/apps-ui-kit/components/access-right-select";
import type { TOption } from "@onlyoffice/apps-ui-kit/components/combobox";
const OPTIONS: TOption[] = [
{ key: "manager", label: "Room manager", access: 1 },
{ key: "editor", label: "Editor", access: 2 },
{ key: "viewer", label: "Viewer", access: 3 },
];
export function LimitedAccess() {
const [access, setAccess] = useState<TOption>(OPTIONS[2]);
return (
<AccessRightSelect
accessOptions={OPTIONS}
selectedOption={access}
isSelectionDisabled
availableAccess={[2, 3]}
selectionErrorText="Your plan does not include room managers"
onSelect={(option) => setAccess(option)}
/>
);
}
Behaviour the types don't state
- A refused choice is reported by a toast, not to your code. With
isSelectionDisabled, an option outsideavailableAccessshowsselectionErrorTextthroughtoastr.errorandonSelectis never called — soToasthas to be mounted or the refusal is silent. advancedOptionsreplacesaccessOptionsoutright. Pass it and the access rows are not built at all; you are rendering the drop-down's contents yourself.type: "onlyIcon"blanks the label and the description of the selected option before handing it to the combo button, so the button shows the icon alone. The rows in the list keep their text.- The selection is mirrored, not owned.
selectedOptionseeds the component's state and is re-applied whenever it changes, and the state also moves on its own when a row is clicked. - Only
icons that are strings are drawn. They are rendered throughReactSVGas a URL; an element passed as an icon is dropped. - An option's
quotaturns into a paid badge beside the label, coloured by itscolor. - The underlying ComboBox is always given an empty
optionsarray — every row goes throughadvancedOptions— so props that act onoptionshave nothing to act on. topSpace,modernView,fillIcon,isDefaultMode,comboIcon,usePortalBackdropandsetIsOpenItemAccessare forwarded toComboBoxuntouched and are documented there.- The component is memoised, so a new
accessOptionsarray built inline on every render defeats the memo without changing anything.
CSS variables
Set these on an ancestor to retheme the rows and the icon-only button. Everything else the stylesheet defines is private to it.
The button is a ComboBox and the list a
DropDown, and they take those components' variables —
--combobox-radius for the button's corners, --dropdown-bg, --dropdown-border-style,
--dropdown-shadow and --dropdown-radius for the panel.
The list renders in a portal at the end of the page by default, out of reach of a wrapper's
variables, so the row and panel variables set on a wrapper only apply with
isDefaultMode={false}, which renders it in place. With the portal, set them on body or
:root.
Accessibility
- The roles are
ComboBox's. The button is adivwithrole="button",aria-haspopup="listbox"andaria-expanded, and it is in the tab order; the list is arole="listbox"ofrole="option"rows, each carryingaria-selectedandaria-disabled, and a divider is arole="separator". - The button opens on click only. Enter and Space do nothing on it, and the arrow keys and Enter do not move through these rows, because the rows are not the combo box's own options.
- The refusal message is a toast, which is easy to miss and is not tied to the control.
- The second line of each row is plain text, not a description associated with the option, so it is read as part of the row rather than as help.
- Give the control a name through the surrounding markup; nothing here labels it.
Test ids
| Element | data-testid |
|---|---|
| An option | access_right_option_<key>, lower-cased |
| The combo button | dataTestId, passed to ComboBox |
Related
ComboBox— what this is built on, and where the forwarded props live.DropDownItem— the rows it builds.Toast— where a refused choice is reported.