Fill frontend rule gaps and broaden v2 tokens
Add the frontend guides the v2 UI kit and compliance-portal need but that the first rework left uncovered: forms, routing, client state, and permission-gated UI. forms.md documents a tiered approach on Base UI Field/Form -- native constraints, then a validate function, then zod parsed in onSubmit, and react-hook-form only for large or dynamic forms -- and drops the custom useFormWithSchema wrapper. routing.md covers @probo/routes, navigation, typed params, URL-as-state, redirects, auth/protected routes, and the folded-in no-outlet-context rule. state-management.md gives a decision order across Relay, URL, local state, context, and zustand. permissions.md gates UI on the canUpdate/canDelete permission(action:) fields without re-encoding authorization in the client. Rename v2-colors.md to v2-tokens.md and add the typography, radius, shadow, and native-spacing scales alongside color. Extend ui.md with user feedback, empty-state, and accessibility sections; standardize toasts on Base UI's Toast (Toast.useToastManager) and retire the legacy useToast across ui.md, forms.md, error-handling.md, and relay.md. Add an Intl formatting section to i18n.md and a non-Relay HTTP / file upload-download section to ts-style.md. Update the AGENTS.md index and the v2-color-scale cursor rule for the new and renamed guides. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -210,6 +210,8 @@ function ContactListItem(props: { contactKey: ContactListItem_contactFragment$ke
|
||||
}
|
||||
```
|
||||
|
||||
Select `permission(action:)` fields (aliased `canUpdate` / `canDelete`) in the fragment of the component that renders the action, and gate the UI on the resulting boolean. See [`contrib/claude/permissions.md`](permissions.md).
|
||||
|
||||
### Refetchable fragments
|
||||
|
||||
For lists that support sorting and pagination, use `@refetchable` with `@argumentDefinitions`:
|
||||
@@ -297,30 +299,29 @@ createCookieBanner({ variables: { ... } });
|
||||
const [deleteThirdParty] = useMutation<ThirdPartyGraphDeleteMutation>(deleteThirdPartyMutation);
|
||||
```
|
||||
|
||||
For mutations with user feedback, combine with `useToast` and use `onCompleted`/`onError` callbacks:
|
||||
For mutations with user feedback, queue a toast with Base UI's toast manager (`Toast.useToastManager()`; see [`ui.md`](ui.md#user-feedback-toasts)) from the `onCompleted` / `onError` callbacks:
|
||||
|
||||
```tsx
|
||||
const { toast } = useToast();
|
||||
const toast = Toast.useToastManager();
|
||||
const [createObligation, isCreating] = useMutation<CreateObligationMutation>(createObligationMutation);
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
const onSubmit = (input: ObligationInput) => {
|
||||
createObligation({
|
||||
variables: {
|
||||
input: { ...formData },
|
||||
input,
|
||||
connections: [connectionId],
|
||||
},
|
||||
onCompleted() {
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __("Obligation created successfully"),
|
||||
variant: "success",
|
||||
toast.add({
|
||||
title: t("obligations.created"),
|
||||
type: "success",
|
||||
});
|
||||
},
|
||||
onError(error) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: formatError(__("Failed to create obligation"), error as GraphQLError),
|
||||
variant: "error",
|
||||
toast.add({
|
||||
title: t("common.error"),
|
||||
description: formatError(t("obligations.createFailed"), error as GraphQLError),
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -329,7 +330,7 @@ const onSubmit = (formData: FormData) => {
|
||||
|
||||
### `useMutationWithToasts` (deprecated)
|
||||
|
||||
**Do not use.** Use `useMutation` combined with `useToast` instead.
|
||||
**Do not use.** Use `useMutation` and queue feedback with Base UI's toast manager (see [`ui.md`](ui.md#user-feedback-toasts)).
|
||||
|
||||
### `promisifyMutation` (deprecated)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user