Skip to content

Commit

Permalink
chore: replace redundant useManagedState with Vue's defineModel (#2529)
Browse files Browse the repository at this point in the history
The changes are mostly internal, but the typings were of `OnyxSelect`
have changed:

- The `modelValue` now infers a specific subtype of `SelectOptionValue`
and the `options` values must match.
- `withSearch`: Filtering of the options will not automatically disabled
anymore when `searchTerm` is bound. Instead `noFilter` must be set.
- removed `Arrayable` type, imported from `vitest`, as it was an
internal type. Replaced with our own type with the same name.
- removed redundant `MaybeReactiveSource` type, as it is now covered by
Vue's `MaybeRefOrGetter` type
  • Loading branch information
JoCa96 authored Jan 21, 2025
1 parent 2f0e5cf commit ece5641
Show file tree
Hide file tree
Showing 26 changed files with 153 additions and 373 deletions.
11 changes: 11 additions & 0 deletions .changeset/three-hornets-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@sit-onyx/headless": patch
"sit-onyx": major
---

chore: replace redundant useManagedState with defineModel

The changes are mostly internal, but the typings were of `OnyxSelect` were improved:

- The `modelValue` now infers a specific subtype of `SelectOptionValue` and the `options` values must match.
- `withSearch`: Filtering of the options will not automatically disabled anymore when `searchTerm` is bound. Instead `noFilter` must be set.
16 changes: 9 additions & 7 deletions apps/demo-app/src/components/SelectDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { normalizedIncludes, OnyxSelect, type SelectOption } from "sit-onyx";
import { computed, ref } from "vue";
defineProps<{
selectOptions: SelectOption[];
selectOptions: SelectOption<string>[];
useSkeleton: boolean;
}>();
const groupedSelectOptions: SelectOption[] = [
const groupedSelectOptions = [
{ value: "cat", label: "Cat", group: "Land" },
{ value: "dog", label: "Dog", group: "Land" },
{ value: "tiger", label: "Tiger", group: "Land" },
Expand All @@ -18,22 +18,22 @@ const groupedSelectOptions: SelectOption[] = [
{ value: "eel", label: "Eel", group: "Water" },
{ value: "falcon", label: "Falcon", group: "Air" },
{ value: "owl", label: "Owl", group: "Air" },
];
] satisfies SelectOption[];
const selectState = ref<string>();
const groupedSelectState = ref<string>();
const multiselectState = ref<string[]>();
const lazyLoadedState = ref(15);
const lazyLoadedLength = ref(10);
const lazyLoadedOptions = computed<SelectOption[]>(() =>
const lazyLoadedOptions = computed<SelectOption<number>[]>(() =>
Array.from({ length: lazyLoadedLength.value }, (_, value) => ({
value,
label: `Lazy option ${value}`,
})),
);
const filteredState = ref(3);
const filteredState = ref("3");
const filterSearchTerm = ref("");
const filterBase = [
{ value: "0", label: "Option Zero" },
Expand All @@ -43,8 +43,10 @@ const filterBase = [
{ value: "4", label: "Option Four" },
{ value: "5", label: "Option Five" },
];
const filterValueLabel = computed(() => filterBase[filteredState.value].label);
const filteredOptions = computed<SelectOption[]>(() =>
const filterValueLabel = computed(
() => filterBase.find(({ value }) => value === filteredState.value)?.label,
);
const filteredOptions = computed(() =>
filterBase.filter(
({ value, label }) =>
normalizedIncludes(label, filterSearchTerm.value) || value === filterSearchTerm.value,
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-app/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const selectOptions = [
"Melon",
"Raspberry",
"Strawberry",
].map<SelectOption>((option) => ({ value: option.toLowerCase(), label: option }));
].map((option) => ({ value: option.toLowerCase(), label: option }));
const minimalSelectOptions = selectOptions.slice(0, 3);
Expand Down
7 changes: 3 additions & 4 deletions packages/headless/src/composables/helpers/useOutsideClick.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { Arrayable } from "vitest";
import { toValue, type Ref } from "vue";
import type { MaybeReactiveSource } from "../../utils/types";
import type { Arrayable } from "vitest"; // For an unknown reason removing this import will break the build of "demo-app" and "playground"
import { toValue, type MaybeRefOrGetter, type Ref } from "vue";
import { useGlobalEventListener } from "./useGlobalListener";

export type UseOutsideClickOptions = {
/**
* HTML element of the component where clicks should be ignored
*/
inside: MaybeReactiveSource<Arrayable<HTMLElement | undefined>>;
inside: MaybeRefOrGetter<Arrayable<HTMLElement | undefined>>;
/**
* Callback when an outside click occurred.
*/
Expand Down
6 changes: 2 additions & 4 deletions packages/headless/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { ComputedRef, MaybeRefOrGetter } from "vue";

/**
* Adds the entry with the key `Key` and the value of type `TValue` to a record when it is defined.
* Then the entry is either undefined or exists without being optional.
Expand All @@ -25,6 +23,6 @@ export type IsArray<TValue, TMultiple extends boolean = false> = TMultiple exten
: TValue;

/**
* Type for any kind of ref source. Preferably used in combination with vue's `toValue` method
* A type that can be wrapped in an array.
*/
export type MaybeReactiveSource<T> = MaybeRefOrGetter<T> | ComputedRef<T>;
export type Arrayable<T> = T | Array<T>;
9 changes: 0 additions & 9 deletions packages/sit-onyx/.storybook/managed.ts

This file was deleted.

9 changes: 4 additions & 5 deletions packages/sit-onyx/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createPreview } from "@sit-onyx/storybook-utils";
import { createPreview, withVModelDecorator } from "@sit-onyx/storybook-utils";
import { setup, type Preview } from "@storybook/vue3";
import { createToastProvider, TOAST_PROVIDER_INJECTION_KEY } from "../src";
import docsTemplate from "./docs-template.mdx";
Expand All @@ -12,9 +12,8 @@ import { a11yTags } from "../src/a11yConfig";
import "../src/styles/index.scss";
import "./docs-template.scss";
import { enhanceFormInjectedSymbol } from "./formInjected";
import { enhanceManagedSymbol } from "./managed";
import brandImage from "./public/onyx-logo-long.svg";
import { withOnyxVModelDecorator } from "./vModel";

const enabledRules = getRules(a11yTags).map((ruleMetadata) => ({
id: ruleMetadata.ruleId,
enabled: true,
Expand All @@ -24,7 +23,7 @@ const axeConfig: Spec = { rules: enabledRules };

const basePreview = createPreview(
{
argTypesEnhancers: [enhanceManagedSymbol, enhanceFormInjectedSymbol],
argTypesEnhancers: [enhanceFormInjectedSymbol],
parameters: {
docs: {
page: docsTemplate,
Expand All @@ -42,7 +41,7 @@ const basePreview = createPreview(
globalTypes: {
...onyxThemeGlobalType,
},
decorators: [withOnyxTheme, withOnyxVModelDecorator],
decorators: [withOnyxTheme, withVModelDecorator()],
},
{
brandImage,
Expand Down
16 changes: 0 additions & 16 deletions packages/sit-onyx/.storybook/vModel.ts

This file was deleted.

19 changes: 6 additions & 13 deletions packages/sit-onyx/src/components/OnyxMiniSearch/OnyxMiniSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,22 @@ defineOptions({ inheritAttrs: false });
const props = defineProps<OnyxMiniSearchProps>();
const emit = defineEmits<{
/**
* Emitted when the current search value changes.
*/
"update:modelValue": [input: string];
/**
* Emitted when the clear button is clicked.
*/
clear: [];
}>();
/**
* Current input/search value.
*/
const modelValue = defineModel<string>({ default: "" });
const { rootAttrs, restAttrs } = useRootAttrs();
const { densityClass } = useDensity(props);
const { t } = injectI18n();
const input = ref<HTMLInputElement>();
/**
* Current value (with getter and setter) that can be used as "v-model" for the native input.
*/
const value = computed({
get: () => props.modelValue,
set: (value) => emit("update:modelValue", value ?? ""),
});
const placeholder = computed(() => t.value("select.searchPlaceholder"));
defineExpose({
Expand All @@ -54,7 +47,7 @@ defineExpose({
>
<input
ref="input"
v-model="value"
v-model="modelValue"
class="onyx-mini-search__input onyx-text"
:placeholder="placeholder"
type="text"
Expand Down
4 changes: 0 additions & 4 deletions packages/sit-onyx/src/components/OnyxMiniSearch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,4 @@ export type OnyxMiniSearchProps = DensityProp & {
* (Aria) label of the input.
*/
label: string;
/**
* Current input/search value.
*/
modelValue?: string;
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
<script setup lang="ts" generic="TValue extends SelectOptionValue = SelectOptionValue">
import { createMenuButton } from "@sit-onyx/headless";
import { computed, toRef } from "vue";
import { MANAGED_SYMBOL, useManagedState } from "../../../../composables/useManagedState";
import { computed } from "vue";
import type { SelectOptionValue } from "../../../../types";
import type { OnyxFlyoutMenuProps } from "./types";
const props = withDefaults(defineProps<OnyxFlyoutMenuProps>(), {
open: MANAGED_SYMBOL,
trigger: "hover",
});
const emit = defineEmits<{
"update:open": [isOpen: boolean];
}>();
const { state: isExpanded } = useManagedState(
toRef(() => props.open),
false,
(newVal) => emit("update:open", newVal),
);
/**
* If the flyout is expanded or not.
*/
const isExpanded = defineModel<boolean>("open", { default: false });
const slots = defineSlots<{
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import type { ManagedProp } from "../../../../composables/useManagedState";

export type OnyxFlyoutMenuProps = {
/**
* If the flyout is expanded or not.
* If `undefined`, the state will be managed internally.
*/
open?: ManagedProp<boolean>;
/**
* If the flyout is expanded on click or hover.
* The default value is 'hover' which will expand the flyout on hover.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ const slots = defineSlots<{
const { t } = injectI18n();
const emit = defineEmits<{
"update:mobileChildrenOpen": [isOpen: boolean];
}>();
const mobileChildrenOpen = defineModel<boolean>("mobileChildrenOpen", { default: false });
</script>

<template>
Expand All @@ -46,7 +44,7 @@ const emit = defineEmits<{
mode="plain"
color="neutral"
:icon="arrowSmallLeft"
@click="emit('update:mobileChildrenOpen', false)"
@click="mobileChildrenOpen = false"
/>

<slot
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script lang="ts" setup>
import chevronRightSmall from "@sit-onyx/icons/chevron-right-small.svg?raw";
import { computed, inject, toRef } from "vue";
import { MANAGED_SYMBOL, useManagedState } from "../../../../composables/useManagedState";
import { computed, inject } from "vue";
import { useMoreListChild } from "../../../../composables/useMoreList";
import OnyxExternalLinkIcon from "../../../OnyxExternalLinkIcon/OnyxExternalLinkIcon.vue";
import OnyxIcon from "../../../OnyxIcon/OnyxIcon.vue";
Expand All @@ -12,18 +11,13 @@ import type { OnyxNavButtonProps } from "./types";
const props = withDefaults(defineProps<OnyxNavButtonProps>(), {
active: false,
withExternalIcon: "auto",
mobileChildrenOpen: MANAGED_SYMBOL,
});
const emit = defineEmits<{
/**
* Emitted when the nav button is clicked (via click or keyboard).
*/
navigate: [href: string, event: MouseEvent];
/**
* Emitted when the mobile children are open or closed.
*/
"update:mobileChildrenOpen": [isOpen: boolean];
}>();
const slots = defineSlots<{
Expand All @@ -37,19 +31,18 @@ const slots = defineSlots<{
children?(): unknown;
}>();
/**
* Controls the open state for the mobile children.
*/
const mobileChildrenOpen = defineModel<boolean>("mobileChildrenOpen", { default: false });
const isMobile = inject(
MOBILE_NAV_BAR_INJECTION_KEY,
computed(() => false),
);
const hasChildren = computed(() => !!slots.children);
const { componentRef, isVisible } = useMoreListChild(NAV_BAR_MORE_LIST_INJECTION_KEY);
const { state: mobileChildrenOpen } = useManagedState(
toRef(() => props.mobileChildrenOpen),
false,
(newVal) => emit("update:mobileChildrenOpen", newVal),
);
const handleParentClick = (event: MouseEvent) => {
if (isMobile?.value && hasChildren.value && !mobileChildrenOpen.value) {
mobileChildrenOpen.value = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import type { ManagedProp } from "../../../../composables/useManagedState";
import type { OnyxExternalLinkIcon } from "../../../OnyxExternalLinkIcon/types";

export type OnyxNavButtonProps = OnyxExternalLinkIcon & {
/**
* Controls the open state for the mobile children.
* Is managed internally if not provided.
*/
mobileChildrenOpen?: ManagedProp<boolean>;
/**
* Label to show inside the Nav item.
* You can use the `default` slot to display custom content.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
<script lang="ts" setup>
import { computed, inject } from "vue";
import { MANAGED_SYMBOL } from "../../../../composables/useManagedState";
import OnyxAvatar from "../../../OnyxAvatar/OnyxAvatar.vue";
import { MOBILE_NAV_BAR_INJECTION_KEY } from "../../types";
import type { OnyxUserMenuProps } from "./types";
import UserMenuLayout from "./UserMenuLayout.vue";
const props = withDefaults(defineProps<OnyxUserMenuProps>(), { flyoutOpen: MANAGED_SYMBOL });
const emit = defineEmits<{
"update:flyoutOpen": [isOpen: boolean];
}>();
const props = defineProps<OnyxUserMenuProps>();
const slots = defineSlots<{
/**
Expand All @@ -23,6 +18,11 @@ const slots = defineSlots<{
footer?(): unknown;
}>();
/**
* If the flyout is expanded or not. Only has an effect in desktop (non-mobile) mode.
*/
const flyoutOpen = defineModel<boolean>("flyoutOpen", { default: false });
const avatar = computed(() => {
return { src: props.avatar, label: props.username };
});
Expand All @@ -35,11 +35,10 @@ const isMobile = inject(

<template>
<UserMenuLayout
v-model:flyout-open="flyoutOpen"
class="onyx-component onyx-user-menu"
:class="{ 'onyx-user-menu--mobile': isMobile }"
:is-mobile="isMobile"
:flyout-open="flyoutOpen"
@update:flyout-open="emit('update:flyoutOpen', $event)"
>
<template #button="{ trigger }">
<button class="onyx-user-menu__trigger onyx-text" type="button" v-bind="trigger">
Expand Down
Loading

0 comments on commit ece5641

Please sign in to comment.