Skip to content

feature/FE-2810#87

Open
RhodriJonesUpmind wants to merge 12 commits into
mainfrom
feature/FE-2810
Open

feature/FE-2810#87
RhodriJonesUpmind wants to merge 12 commits into
mainfrom
feature/FE-2810

Conversation

@RhodriJonesUpmind

Copy link
Copy Markdown
Contributor

No description provided.

  Interactive components can now pick up `disabled` and `readonly`
  state from any ancestor via provide/inject, eliminating the need
  to wire those props at every call site.

  Adds `DISABLED_KEY`/`useDisabled`/`provideDisabled` and the same
  for readonly in `src/utils/`. Each composable returns a resolved
  `ComputedRef<boolean>` that ORs the local source with any
  ancestor-provided value. Either key can be provided at any level
  (app shell, layout, page, section); the closest ancestor wins.

  Helper components `<DisabledProvider>` and `<ReadonlyProvider>`
  make declarative scoping ergonomic.

  Wired into 22 disabled-aware components (button, button-group,
  input, input-password, textarea, number-field, select,
  select-grouped, select-cards, combobox, autocomplete, checkbox,
  checkbox-cards, checkbox-group, radio-cards,
  radio-cards-collapsible, radio-group, switch, toggle, command,
  search, link) and 3 readonly-aware (input, input-password,
  textarea). Select-family and number-field readonly deferred — the
  underlying radix-vue primitives don't expose `readonly`.

  Existing `disabled`/`readonly` props continue to work standalone;
  the cascade is OR'd with the prop, so behaviour is unchanged when
  no provider exists.
  Input had no visual disabled treatment — functionally `:disabled`
  on the native input worked, but the container showed no opacity
  dim, cursor change, or hover suppression. The cascade landed in
  2948dd6 (FE-2810) exposed this because the input now gets
  disabled more often via ambient context.

  Mirrors the radio-cards pattern: sets `data-disabled` on the
  container from the resolved disabled state, and extends the
  container variants with `data-[disabled]:pointer-events-none
  data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50`
  Form's processing/disabled state only reached JsonForms-rendered
  controls via the `:enabled` prop. Non-JsonForms slot content
  (e.g. `<Manage>` rendering `<RadioCardsCollapsible>` for address
  selection) only consumed the ancestor disabled cascade, missing
  Form's own processing signal — so during a form submit, text
  inputs stayed locked but the address cards briefly enabled,
  creating a visible desync.

  Form now calls `provideDisabled(isDisabled)` after its existing
  `useDisabled(...)` resolution, becoming a disabled boundary:
  descendants inject Form's resolved state (cascade ∪ disabled ∪
  processing) rather than the cascade alone.

  Standard pattern — Vuetify's `<v-form>` and FormKit's `<FormKit>`
  do the same for the same reason.
  Replaces the `:data-disabled="isDisabled || undefined"` + Tailwind
  `data-[disabled]:*` pattern with a proper CVA `isDisabled` variant
  on Input, RadioCards, and RadioCardsCollapsible item wrappers.

  Disabled visual now flows: `useDisabled()` → `meta.isDisabled` →
  CVA variant → classes. No DOM data-attribute, no Tailwind arbitrary
  selector, no `|| undefined` idiom. Matches how `Button.ce.vue`
  handles its own disabled visual and aligns with the "classes live
  in `*.config.ts` + `useStyles`" convention.
  `DisabledProvider` / `ReadonlyProvider` were slot-only wrappers around
  `provideDisabled()` / `provideReadonly()` for template-scoped cascade
  provisioning. Zero usages found across packages/ and apps/ — every
  actual provide site (UpmPage, Form) calls the function directly from
  `<script setup>`.

  Removes:
  - `packages/ui/src/ui/disabled-provider/`
  - `packages/ui/src/ui/readonly-provider/`
  - Two top-level `export *` re-exports

  Trivial to re-add (~10 LOC per component) if a template-scoped use
  case ever materialises — keeping surface area minimal for now.
  Earlier added Form `provideDisabled(isDisabled)` on the assumption
  that non-JsonForms slot content (RadioCardsCollapsible in Manage)
  needed Form's processing signal via the cascade. That assumption
  was wrong:

  - Manage renders the list and edit-form as siblings — the radio
    cards aren't inside any `<Form>`.
  - JsonForms-rendered controls inside Form already get disabled via
    `:enabled="!meta.isDisabled"` → renderer's
    `appliedOptions.disabled` → Input's own `useDisabled` ORs with
    the cascade.
  - Default action buttons read `meta.isProcessing` explicitly.
  - new shimmer-mask utility: alternating CSS-mask sweep over currentColor
  - loading implies disabled (Button parity via useDisabled)
  - swap to span + click guard while disabled so cursor-wait/not-allowed
    show without pointer-events hacks
* main:
  fix ui-config cva type error
  feat(ui): forward Input ui-config to InputItems for renderer-owned icon styling
  fix(ButtonProps): allow 'link' as a variant option
    BreadcrumbItem gains `loading`, forwarded to the clickable Link so a
    single crumb can shimmer while its navigation is in flight. The Link
    now also invokes `item.handler` on click — the field existed on the
    public type but was never wired, so handler-driven consumers
    (useBreadcrumbs onSelect / pending tracking) can finally react to
    clicks. Link's loading guard already blocks repeat clicks.
…g flag

  `:loading="meta.isProcessing"` came after `v-bind="action"`, clobbering
  any `loading` the caller passed on the action.
…ith disabled (FE-2810)

  Form forwarded its raw `readonly` prop straight to JsonForms while `disabled`
  went through `useDisabled` (local prop OR ancestor-provided). Route `readonly`
  through `useReadonly(() => props.readonly)` too, so both resolve the same
  cascade-aware way. No behaviour change today (nothing provides readonly yet),
  but Form is now consistent and ready to inherit an ancestor `provideReadonly`.

@dominicpedro dominicpedro left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general i would prefer the const isDisabled = to jsut be const disabled = as the readonly ref, and rthen using isDisabled in meta. i dont like is/has/etc as standalone computeds.

:loading="item.loading"
color="muted"
:uiConfig="{ link: { items: '[&>i]:p-[3px]' } } as any"
@click="item.handler?.()"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this need s a proper onClick so we dont fire the handler when disabled. like we do in link.. i nfact wh yare we not using the Link Action?

Comment thread src/utils/useDisabled.ts Outdated
* Provides a disabled signal to descendants. Accepts a plain boolean or a ref.
*/
export const provideDisabled = (value: MaybeRef<boolean>): void => {
provide(DISABLED_KEY, value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to use provide if we are using a composible?

Comment thread src/utils/useReadonly.ts Outdated
* Provides a readonly signal to descendants. Accepts a plain boolean or a ref.
*/
export const provideReadonly = (value: MaybeRef<boolean>): void => {
provide(READONLY_KEY, value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. i dont think we need provide inject if we have a composible. we can jsut create a singleton and expose i nthe return

  useDisabled/useReadonly used provide/inject, so the cascade could only be seeded
  from inside a component setup(). Replace the InjectionKey with a module-level
  singleton: each composable holds a Set of contributed sources and resolves
  `local source || any source`. Works outside component setup, no provider ancestor.

  - provideDisabled/provideReadonly -> setDisabled/setReadonly (register a source,
    return a stop fn, auto-clean on scope dispose); DISABLED_KEY/READONLY_KEY removed.
  - useDisabled/useReadonly signatures unchanged, so consumer call sites are untouched.
  - A Set (not a single ref) so multiple contributors OR together instead of
    clobbering; the cascade is now global rather than subtree-scoped.

  Also surface isReadonly in Form's meta and FormMeta, alongside isDisabled, so the
  readonly state is exposed the same is-prefixed way (review consistency).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants