nuance-ui 0.2.17 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/alert.vue +1 -1
  3. package/dist/runtime/components/app-shell/app-shell-main.vue +1 -1
  4. package/dist/runtime/components/app-shell/app-shell.vue +1 -1
  5. package/dist/runtime/components/badge.d.vue.ts +4 -1
  6. package/dist/runtime/components/badge.vue +5 -2
  7. package/dist/runtime/components/badge.vue.d.ts +4 -1
  8. package/dist/runtime/components/button/button.module.css +1 -1
  9. package/dist/runtime/components/center.d.vue.ts +18 -0
  10. package/dist/runtime/components/center.vue +26 -0
  11. package/dist/runtime/components/center.vue.d.ts +18 -0
  12. package/dist/runtime/components/floating-indicator.d.vue.ts +20 -0
  13. package/dist/runtime/components/floating-indicator.vue +50 -0
  14. package/dist/runtime/components/floating-indicator.vue.d.ts +20 -0
  15. package/dist/runtime/components/group.d.vue.ts +28 -0
  16. package/dist/runtime/components/group.vue +39 -0
  17. package/dist/runtime/components/group.vue.d.ts +28 -0
  18. package/dist/runtime/components/index.d.ts +2 -0
  19. package/dist/runtime/components/input/ui/input-base.d.vue.ts +1 -0
  20. package/dist/runtime/components/input/ui/input-base.vue +3 -2
  21. package/dist/runtime/components/input/ui/input-base.vue.d.ts +1 -0
  22. package/dist/runtime/components/link/lib.d.ts +2 -2
  23. package/dist/runtime/components/nav-link/nav-link.vue +1 -1
  24. package/dist/runtime/components/paper.vue +1 -1
  25. package/dist/runtime/components/pin-input/lib.d.ts +2 -0
  26. package/dist/runtime/components/pin-input/lib.js +19 -0
  27. package/dist/runtime/components/pin-input/pin-input.d.vue.ts +55 -0
  28. package/dist/runtime/components/pin-input/pin-input.vue +171 -0
  29. package/dist/runtime/components/pin-input/pin-input.vue.d.ts +55 -0
  30. package/dist/runtime/components/pin-input/use-pin-input.d.ts +18 -0
  31. package/dist/runtime/components/pin-input/use-pin-input.js +94 -0
  32. package/dist/runtime/components/roving-focus/lib/context.d.ts +2 -2
  33. package/dist/runtime/components/segmented-control.d.vue.ts +46 -0
  34. package/dist/runtime/components/segmented-control.vue +130 -0
  35. package/dist/runtime/components/segmented-control.vue.d.ts +46 -0
  36. package/dist/runtime/components/stack.d.vue.ts +24 -0
  37. package/dist/runtime/components/stack.vue +33 -0
  38. package/dist/runtime/components/stack.vue.d.ts +24 -0
  39. package/dist/runtime/components/switch/switch.vue +1 -1
  40. package/dist/runtime/components/table/ui/table.vue +1 -1
  41. package/dist/runtime/components/theme-toggle.d.vue.ts +14 -0
  42. package/dist/runtime/components/theme-toggle.vue +27 -0
  43. package/dist/runtime/components/theme-toggle.vue.d.ts +14 -0
  44. package/dist/runtime/composables/index.d.ts +1 -0
  45. package/dist/runtime/composables/index.js +1 -0
  46. package/dist/runtime/composables/use-floating-indicator.d.ts +11 -0
  47. package/dist/runtime/composables/use-floating-indicator.js +30 -0
  48. package/dist/runtime/form/segmented-control-field.d.vue.ts +7 -0
  49. package/dist/runtime/form/segmented-control-field.vue +45 -0
  50. package/dist/runtime/form/segmented-control-field.vue.d.ts +7 -0
  51. package/dist/runtime/styles/colors.css +1 -1
  52. package/dist/runtime/styles/const.css +1 -1
  53. package/dist/runtime/styles/dark-theme.css +1 -1
  54. package/dist/runtime/types/theme.d.ts +3 -3
  55. package/dist/runtime/utils/color/get-color-var.js +1 -2
  56. package/dist/runtime/utils/color/parse-theme-color.js +1 -2
  57. package/package.json +1 -1
@@ -0,0 +1,171 @@
1
+ <script setup>
2
+ import { useVarsResolver } from "@nui/composables";
3
+ import { getSize } from "@nui/utils";
4
+ import { computed, useId } from "vue";
5
+ import Group from "../group.vue";
6
+ import InputBase from "../input/ui/input-base.vue";
7
+ import VisuallyHiddenInput from "../visually-hidden/visually-hidden-input.vue";
8
+ import { createPinArray, pinValidate } from "./lib";
9
+ import { usePinInput } from "./use-pin-input";
10
+ const {
11
+ id,
12
+ name,
13
+ disabled,
14
+ required,
15
+ length = 4,
16
+ otp = true,
17
+ type = "number",
18
+ mask,
19
+ gap,
20
+ placeholder,
21
+ autoFocus,
22
+ manageFocus = true,
23
+ size,
24
+ radius = "md",
25
+ readOnly,
26
+ error
27
+ } = defineProps({
28
+ id: { type: String, required: false },
29
+ name: { type: String, required: false },
30
+ gap: { type: [String, Number], required: false },
31
+ radius: { type: [String, Number], required: false },
32
+ size: { type: String, required: false },
33
+ autoFocus: { type: Boolean, required: false },
34
+ placeholder: { type: String, required: false },
35
+ manageFocus: { type: Boolean, required: false },
36
+ otp: { type: Boolean, required: false },
37
+ disabled: { type: Boolean, required: false },
38
+ error: { type: Boolean, required: false },
39
+ type: { type: null, required: false },
40
+ mask: { type: Boolean, required: false },
41
+ length: { type: Number, required: false },
42
+ readOnly: { type: Boolean, required: false },
43
+ ariaLabel: { type: String, required: false },
44
+ required: { type: Boolean, required: false },
45
+ is: { type: null, required: false },
46
+ mod: { type: [Object, Array, null], required: false }
47
+ });
48
+ defineEmits(["complete"]);
49
+ const uuid = computed(() => id ?? useId());
50
+ const inputMode = computed(() => type === "number" ? "numeric" : "text");
51
+ const model = defineModel({ type: String, ...{ default: "" } });
52
+ const {
53
+ refs,
54
+ focus,
55
+ focusedIx,
56
+ cells,
57
+ setFieldValue,
58
+ handleKeydown
59
+ } = usePinInput({
60
+ length: () => length,
61
+ manageFocus,
62
+ value: model,
63
+ inputMode
64
+ });
65
+ function handleInput(event, ix) {
66
+ const target = event.target;
67
+ const inputValue = target.value;
68
+ if (inputValue.length > 1) {
69
+ const isPaste = inputValue.length > 2;
70
+ if (isPaste) {
71
+ const isValid = pinValidate(inputValue, type);
72
+ if (!isValid)
73
+ return;
74
+ cells.value = createPinArray(length, inputValue);
75
+ const filledCount = Math.min(inputValue.length, length);
76
+ if (filledCount < length)
77
+ focus("next", filledCount - 1);
78
+ return;
79
+ }
80
+ const newChar = inputValue.split("")[inputValue.length - 1];
81
+ if (pinValidate(newChar, type)) {
82
+ setFieldValue(newChar, ix);
83
+ focus("next", ix);
84
+ }
85
+ return;
86
+ }
87
+ if (inputValue.length === 1) {
88
+ if (pinValidate(inputValue, type)) {
89
+ setFieldValue(inputValue, ix);
90
+ focus("next", ix);
91
+ } else {
92
+ setFieldValue("", ix);
93
+ }
94
+ } else if (inputValue.length === 0) {
95
+ setFieldValue("", ix);
96
+ }
97
+ }
98
+ function handlePaste(event) {
99
+ const pasteData = event.clipboardData?.getData("text/plain").replace(/\s+/g, "").trim();
100
+ const isValid = pinValidate(pasteData, type);
101
+ if (!isValid)
102
+ return;
103
+ const pasteArray = createPinArray(length, pasteData);
104
+ cells.value = pasteArray;
105
+ const filledCount = pasteArray.filter((v) => v !== "").length;
106
+ const focusIx = filledCount >= length ? length - 1 : filledCount;
107
+ refs.value[focusIx]?.focus();
108
+ }
109
+ const style = useVarsResolver(() => ({
110
+ root: {
111
+ "--pin-size": getSize(size, "pin-size"),
112
+ "--pin-fz": getSize(size, "pin-fz")
113
+ }
114
+ }));
115
+ </script>
116
+
117
+ <template>
118
+ <Group
119
+ role='group'
120
+ wrap='nowrap'
121
+ :gap
122
+ dir='ltr'
123
+ :class='$style.root'
124
+ :style='style.root'
125
+ >
126
+ <InputBase
127
+ v-for='(ids, ix) in length'
128
+ :id='`${uuid}-${ix + 1}`'
129
+ :key='ids'
130
+ :error
131
+ :class='$style.pinRoot'
132
+ :radius
133
+ >
134
+ <template #default='{ id: _id, css }'>
135
+ <input
136
+ :id='_id'
137
+ :key='ids'
138
+ :ref='refs.set'
139
+ :class='[css, $style.pin]'
140
+ :value='cells[ix]'
141
+ :autocomplete='otp ? "one-time-code" : void 0'
142
+ :input-mode
143
+ :type='mask ? "password" : "text"'
144
+ :placeholder
145
+ :auto-focus='autoFocus && ix === 0'
146
+ :readonly='readOnly'
147
+ :disabled
148
+ @input='handleInput($event, ix)'
149
+ @focus='(event) => {
150
+ if (!readOnly) {
151
+ event.target?.select();
152
+ focusedIx = ix;
153
+ }
154
+ }'
155
+ @blur='focusedIx = -1'
156
+ @paste.prevent='handlePaste'
157
+ @keydown='handleKeydown($event, ix)'
158
+ >
159
+ </template>
160
+ </InputBase>
161
+ </Group>
162
+ <VisuallyHiddenInput
163
+ :name
164
+ :disabled
165
+ :required
166
+ />
167
+ </template>
168
+
169
+ <style module>
170
+ .root{--pin-size-xs:rem(30px);--pin-size-sm:rem(36px);--pin-size-md:rem(42px);--pin-size-lg:rem(50px);--pin-size-xl:rem(60px);--pin-size:var(--pin-size-sm);--pin-fz-xs:1.125rem;--pin-fz-sm:1.5rem;--pin-fz-md:1.75rem;--pin-fz-lg:2rem;--pin-fz-xl:2.5rem;--pin-fz:var(--pin-fz-sm)}.pinRoot{@mixin where-light{--input-bd:var(--color-gray-2);--input-bg:var(--color-gray-0)}@mixin where-dark{--input-bd:var(--color-gray-7);--input-bg:var(--color-dark-9)}}.pin{font-size:var(--pin-fz);font-weight:600;height:calc(var(--pin-size) + var(--pin-size)/3);line-height:1;padding:0;text-align:center;width:var(--pin-size)}
171
+ </style>
@@ -0,0 +1,55 @@
1
+ import type { NuanceRadius, NuanceSize, NuanceSpacing } from '../../types/index.js';
2
+ import type { BoxProps } from '../box.vue.js';
3
+ export interface PinInputEmits {
4
+ complete: [value: string];
5
+ }
6
+ export interface PinInputProps extends BoxProps {
7
+ /** Id auto-generated if not provided */
8
+ id?: string;
9
+ /** Hidden input `name` attribute */
10
+ name?: string;
11
+ /** Key of `theme.spacing` or any valid CSS value to set `gap` between inputs, numbers are converted to rem @default 'md' */
12
+ gap?: NuanceSpacing;
13
+ /** Key of `theme.radius` or any valid CSS value to set `border-radius`, numbers are converted to rem @default 'md' */
14
+ radius?: NuanceRadius;
15
+ /** Controls inputs `width` and `height` @default 'sm' */
16
+ size?: NuanceSize;
17
+ /** If set, the first input is focused when component is mounted @default false */
18
+ autoFocus?: boolean;
19
+ /** Inputs placeholder */
20
+ placeholder?: string;
21
+ /** Determines whether focus should be moved automatically to the next input once filled @default true */
22
+ manageFocus?: boolean;
23
+ /** Determines whether `autocomplete="one-time-code"` attribute should be set on all inputs @default true */
24
+ otp?: boolean;
25
+ /** Adds disabled attribute to all inputs */
26
+ disabled?: boolean;
27
+ /** Sets `aria-invalid` attribute and applies error styles to all inputs */
28
+ error?: boolean;
29
+ /** Determines which values can be entered @default 'alphanumeric' */
30
+ type?: 'alphanumeric' | 'number' | RegExp;
31
+ /** Changes input type to `"password"` @default false */
32
+ mask?: boolean;
33
+ /** Number of inputs @default 4 */
34
+ length?: number;
35
+ /** If set, the user cannot edit the value */
36
+ readOnly?: boolean;
37
+ /** `aria-label` attribute */
38
+ ariaLabel?: string;
39
+ /** Marks the field as required */
40
+ required?: boolean;
41
+ }
42
+ type __VLS_Props = PinInputProps;
43
+ type __VLS_ModelProps = {
44
+ modelValue?: string;
45
+ };
46
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
47
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
48
+ "update:modelValue": (value: string) => any;
49
+ complete: (value: string) => any;
50
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
51
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
52
+ onComplete?: ((value: string) => any) | undefined;
53
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
54
+ declare const _default: typeof __VLS_export;
55
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import type { MaybeRefOrGetter, ModelRef } from 'vue';
2
+ export declare function usePinInput<Element extends HTMLInputElement>({ value: model, length, allowedKeys, manageFocus, inputMode, }: {
3
+ value: ModelRef<string>;
4
+ /** current length */
5
+ length: MaybeRefOrGetter<number>;
6
+ /** @default ['Backspace', 'Tab', 'Control', 'Delete', 'ArrowLeft', 'ArrowRight'] */
7
+ allowedKeys?: string[];
8
+ /** @default true */
9
+ manageFocus?: boolean;
10
+ inputMode: MaybeRefOrGetter<'numeric' | 'text'>;
11
+ }): {
12
+ refs: Readonly<import("vue").Ref<Readonly<import("@vueuse/core").TemplateRefsList<Element>>, Readonly<import("@vueuse/core").TemplateRefsList<Element>>>>;
13
+ cells: import("vue").WritableComputedRef<string[], string[]>;
14
+ focusedIx: import("vue").Ref<number, number>;
15
+ focus: (dir: "next" | "prev", ix: number) => void;
16
+ handleKeydown: (event: KeyboardEvent, ix: number) => void;
17
+ setFieldValue: (value: string, ix: number) => string[];
18
+ };
@@ -0,0 +1,94 @@
1
+ import { useTemplateRefsList } from "@vueuse/core";
2
+ import { computed, ref, toValue } from "vue";
3
+ import { createPinArray } from "./lib.js";
4
+ export function usePinInput({
5
+ value: model,
6
+ length,
7
+ allowedKeys = ["Backspace", "Tab", "Control", "Delete", "ArrowLeft", "ArrowRight"],
8
+ manageFocus = true,
9
+ inputMode
10
+ }) {
11
+ const refs = useTemplateRefsList();
12
+ const focusedIx = ref(-1);
13
+ const cells = computed({
14
+ get: () => createPinArray(toValue(length), model.value),
15
+ set: (value) => {
16
+ model.value = value.join("").trim();
17
+ }
18
+ });
19
+ function setFieldValue(value, ix) {
20
+ const values = [...cells.value];
21
+ values[ix] = value;
22
+ cells.value = values;
23
+ return values;
24
+ }
25
+ function focus(dir, ix) {
26
+ if (dir === "next") {
27
+ const nextIx = ix + 1;
28
+ if (nextIx < toValue(length))
29
+ refs.value[nextIx]?.focus();
30
+ } else if (dir === "prev") {
31
+ const prevIx = ix - 1;
32
+ if (prevIx >= 0)
33
+ refs.value[prevIx]?.focus();
34
+ }
35
+ }
36
+ function handleKeydown(event, ix) {
37
+ const inputValue = event.target.value;
38
+ if (inputMode === "numeric") {
39
+ const isModifierShortcut = event.ctrlKey || event.metaKey;
40
+ const isAllowedKey = allowedKeys.includes(event.key) || isModifierShortcut || !Number.isNaN(Number(event.key));
41
+ if (!isAllowedKey)
42
+ return event.preventDefault();
43
+ }
44
+ switch (event.key) {
45
+ case "ArrowLeft":
46
+ event.preventDefault();
47
+ focus("prev", ix);
48
+ break;
49
+ case "ArrowRight":
50
+ event.preventDefault();
51
+ focus("next", ix);
52
+ break;
53
+ case "Tab":
54
+ if (event.shiftKey && ix > 0 && manageFocus) {
55
+ event.preventDefault();
56
+ focus("prev", ix);
57
+ }
58
+ break;
59
+ case " ":
60
+ event.preventDefault();
61
+ focus("next", ix);
62
+ break;
63
+ case "Delete":
64
+ event.preventDefault();
65
+ setFieldValue("", ix);
66
+ break;
67
+ case "Backspace":
68
+ if (inputValue === "") {
69
+ event.preventDefault();
70
+ focus("prev", ix);
71
+ } else {
72
+ setFieldValue("", ix);
73
+ if (ix < toValue(length) - 1) {
74
+ event.preventDefault();
75
+ focus("prev", ix);
76
+ }
77
+ }
78
+ break;
79
+ default:
80
+ if (inputValue.length > 0 && event.key === cells.value[ix]) {
81
+ event.preventDefault();
82
+ focus("next", ix);
83
+ }
84
+ }
85
+ }
86
+ return {
87
+ refs,
88
+ cells,
89
+ focusedIx,
90
+ focus,
91
+ handleKeydown,
92
+ setFieldValue
93
+ };
94
+ }
@@ -20,7 +20,7 @@ export declare const useProvideRovingFocus: (args_0: State) => {
20
20
  attr: string;
21
21
  list: Readonly<ShallowRef<HTMLElement | null>>;
22
22
  loop: boolean | undefined;
23
- orientation: "both" | "horizontal" | "vertical" | undefined;
23
+ orientation: "horizontal" | "vertical" | "both" | undefined;
24
24
  init: () => number;
25
25
  focus: {
26
26
  (dir: "first" | "last"): void;
@@ -38,7 +38,7 @@ export declare const useRovingFocus: () => {
38
38
  attr: string;
39
39
  list: Readonly<ShallowRef<HTMLElement | null>>;
40
40
  loop: boolean | undefined;
41
- orientation: "both" | "horizontal" | "vertical" | undefined;
41
+ orientation: "horizontal" | "vertical" | "both" | undefined;
42
42
  init: () => number;
43
43
  focus: {
44
44
  (dir: "first" | "last"): void;
@@ -0,0 +1,46 @@
1
+ import type { AnyString, NuanceColor, NuanceRadius, NuanceSize } from '@nui/types';
2
+ import type { BoxProps } from './box.vue.js';
3
+ export interface SegmentedControlItem {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ export interface SegmentedControlProps extends BoxProps {
9
+ /** Items to render as controls */
10
+ data: (string | SegmentedControlItem)[];
11
+ /** Component size */
12
+ size?: NuanceSize | AnyString;
13
+ /** Border radius */
14
+ radius?: NuanceRadius;
15
+ /** Color from theme */
16
+ color?: NuanceColor;
17
+ /** Indicator `transition-duration` in ms */
18
+ transitionDuration?: number;
19
+ /** Indicator `transition-timing-function` */
20
+ transitionTimingFunction?: string;
21
+ /** Fills parent width */
22
+ fullWidth?: boolean;
23
+ /** Component orientation */
24
+ orientation?: 'horizontal' | 'vertical';
25
+ /** Disables the component */
26
+ disabled?: boolean;
27
+ /** Prevents value changes */
28
+ readOnly?: boolean;
29
+ /**
30
+ * Show borders between items
31
+ * @default `true`
32
+ */
33
+ withItemsBorders?: boolean;
34
+ }
35
+ type __VLS_Props = SegmentedControlProps;
36
+ type __VLS_ModelProps = {
37
+ modelValue?: string;
38
+ };
39
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
40
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
41
+ "update:modelValue": (value: string | undefined) => any;
42
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
43
+ "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
44
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
45
+ declare const _default: typeof __VLS_export;
46
+ export default _default;
@@ -0,0 +1,130 @@
1
+ <script setup>
2
+ import { useVarsResolver } from "@nui/composables";
3
+ import { getFontSize, getRadius, getSize, getThemeColor } from "@nui/utils";
4
+ import { useTemplateRefsList } from "@vueuse/core";
5
+ import { computed, useId, useTemplateRef } from "vue";
6
+ import Box from "./box.vue";
7
+ import FloatingIndicator from "./floating-indicator.vue";
8
+ const {
9
+ is,
10
+ mod,
11
+ data,
12
+ size,
13
+ radius,
14
+ color,
15
+ transitionDuration,
16
+ transitionTimingFunction,
17
+ fullWidth,
18
+ orientation = "horizontal",
19
+ disabled,
20
+ readOnly,
21
+ withItemsBorders = true
22
+ } = defineProps({
23
+ data: { type: Array, required: true },
24
+ size: { type: [String, Object], required: false },
25
+ radius: { type: [String, Number], required: false },
26
+ color: { type: null, required: false },
27
+ transitionDuration: { type: Number, required: false },
28
+ transitionTimingFunction: { type: String, required: false },
29
+ fullWidth: { type: Boolean, required: false },
30
+ orientation: { type: String, required: false },
31
+ disabled: { type: Boolean, required: false },
32
+ readOnly: { type: Boolean, required: false },
33
+ withItemsBorders: { type: Boolean, required: false },
34
+ is: { type: null, required: false },
35
+ mod: { type: [Object, Array, null], required: false }
36
+ });
37
+ const value = defineModel({ type: String });
38
+ const items = computed(
39
+ () => data.map((item) => typeof item === "string" ? { value: item, label: item } : item)
40
+ );
41
+ if (value.value === void 0) {
42
+ const first = items.value.find((i) => !i.disabled);
43
+ if (first)
44
+ value.value = first.value;
45
+ }
46
+ const groupName = useId();
47
+ const rootRef = useTemplateRef("root");
48
+ const labelRefs = useTemplateRefsList();
49
+ const activeTarget = computed(() => {
50
+ if (value.value === void 0)
51
+ return null;
52
+ const index = items.value.findIndex((i) => i.value === value.value);
53
+ return index !== -1 ? labelRefs.value[index] ?? null : null;
54
+ });
55
+ const style = useVarsResolver((theme) => ({
56
+ root: {
57
+ "--sc-radius": getRadius(radius),
58
+ "--sc-color": color ? getThemeColor(color, theme) : void 0,
59
+ "--sc-shadow": color ? void 0 : "var(--shadow-xs)",
60
+ "--sc-transition-duration": transitionDuration === void 0 ? void 0 : `${transitionDuration}ms`,
61
+ "--sc-transition-timing-function": transitionTimingFunction,
62
+ "--sc-padding": getSize(size, "sc-padding"),
63
+ "--sc-font-size": getFontSize(size)
64
+ }
65
+ }));
66
+ </script>
67
+
68
+ <template>
69
+ <Box
70
+ :is
71
+ ref='root'
72
+ :style='style.root'
73
+ :class='$style.root'
74
+ :mod='[{
75
+ "full-width": fullWidth,
76
+ orientation,
77
+ disabled,
78
+ "with-items-borders": withItemsBorders
79
+ }, mod]'
80
+ role='radiogroup'
81
+ >
82
+ <FloatingIndicator
83
+ v-if='typeof value !== void 0'
84
+ :target='activeTarget'
85
+ :parent='rootRef'
86
+ :class='$style.indicator'
87
+ :orientation
88
+ />
89
+
90
+ <Box
91
+ v-for='item in items'
92
+ :key='item.value'
93
+ :class='$style.control'
94
+ :mod='{ active: value === item.value, orientation }'
95
+ >
96
+ <input
97
+ :id='`${groupName}-${item.value}`'
98
+ :key='`${item.value}-input`'
99
+ :class='$style.input'
100
+ type='radio'
101
+ :name='groupName'
102
+ :value='item.value'
103
+ :checked='value === item.value'
104
+ :disabled='disabled || item.disabled'
105
+ @change='!readOnly && (value = item.value)'
106
+ >
107
+
108
+ <Box
109
+ is='label'
110
+ :ref='labelRefs.set'
111
+ :for='`${groupName}-${item.value}`'
112
+ :class='$style.label'
113
+ :style='{
114
+ "--sc-label-color": !!color && "var(--color-white)"
115
+ }'
116
+ :mod='{
117
+ "active": value === item.value && !(disabled || item.disabled),
118
+ "disabled": disabled || item.disabled,
119
+ "read-only": readOnly
120
+ }'
121
+ >
122
+ <span :class='$style.innerLabel'>{{ item.label }}</span>
123
+ </Box>
124
+ </Box>
125
+ </Box>
126
+ </template>
127
+
128
+ <style module>
129
+ .root{--sc-padding-xs:2px 6px;--sc-padding-sm:3px 10px;--sc-padding-md:4px 14px;--sc-padding-lg:7px 16px;--sc-padding-xl:10px 20px;--sc-padding:var(--sc-padding-sm);--sc-radius:var(--radius-md);--sc-transition-duration:200ms;--sc-transition-timing-function:ease;--sc-font-size:var(--font-size-sm);border-radius:var(--sc-radius,var(--radius-md));display:inline-flex;flex-direction:row;overflow:hidden;padding:4px;position:relative;width:auto}.root:where([data-full-width]){display:flex}.root:where([data-orientation=vertical]){display:flex;flex-direction:column;width:-moz-max-content;width:max-content}.root:where([data-orientation=vertical]):where([data-full-width]){width:auto}.root{@mixin where-light{background-color:var(--color-gray-1)}}.root{@mixin where-dark{background-color:var(--color-dark-8)}}.indicator{border-radius:calc(var(--sc-radius, var(--radius-md)) + 2px);display:block;position:absolute;z-index:1}.indicator:where([data-orientation=horizontal]){bottom:4px;top:4px}.indicator:where([data-orientation=vertical]){left:4px;right:4px}.indicator{@mixin where-light{background-color:var(--sc-color,var(--color-white));box-shadow:var(--sc-shadow,none)}}.indicator{@mixin where-dark{background-color:var(--sc-color,var(--color-dark-5));box-shadow:none}}.label{border-radius:calc(var(--sc-radius, var(--radius-md)) + 2px);cursor:pointer;display:block;font-size:var(--sc-font-size);font-weight:500;outline:var(--segmented-control-outline,none);overflow:hidden;padding:var(--sc-padding);text-align:center;text-overflow:ellipsis;transition:color var(--sc-transition-duration) var(--sc-transition-timing-function);-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap;-webkit-tap-highlight-color:transparent;@mixin where-light{color:var(--color-gray-7)}@mixin where-dark{color:var(--color-dark-1)}}.label:where([data-read-only]){cursor:default}.label:where([data-active]){@mixin where-light{color:var(--sc-label-color,var(--color-black))}@mixin where-dark{color:var(--sc-label-color,var(--color-white))}}.label:where([data-active]):before{border-radius:calc(var(--sc-radius, var(--radius-md)) + 2px);content:"";inset:0;position:absolute;z-index:0}.root:where([data-initialized]) .label:where([data-active]):before{display:none}.label:where([data-active]):before{@mixin where-light{box-shadow:var(--sc-shadow,none)}}.label:where([data-active]):before{@mixin where-dark{box-shadow:none}}.label:where([data-active])[data-disabled],fieldset:disabled .label:where([data-active]){color:var(--color-disabled-text);cursor:not-allowed}.label:where(:not([data-disabled],[data-active],[data-read-only])){@mixin hover{@mixin where-light{color:var(--color-black)}@mixin where-dark{color:var(--color-white)}}}fieldset:disabled .label{@mixin hover{color:var(--color-disabled-text)!important}}.input{height:0;opacity:0;overflow:hidden;position:absolute;white-space:nowrap;width:0}.input:focus-visible+.label{--segmented-control-outline:2px solid var(--color-primary-filled)}.control{flex:1;position:relative;transition:border-color var(--sc-transition-duration) var(--sc-transition-timing-function);z-index:2}.root[data-with-items-borders] :where(.control):before{background-color:var(--separator-color);bottom:0;content:"";inset-inline-start:0;position:absolute;top:0;transition:background-color var(--sc-transition-duration) var(--sc-transition-timing-function);width:1px}.control[data-orientation=vertical]:before{bottom:auto;height:1px;top:0;inset-inline:0;width:auto}.control:first-of-type:before,[data-mantine-color-scheme] .control[data-active]+.control:before,[data-mantine-color-scheme] .control[data-active]:before{--separator-color:transparent}.control{@mixin where-light{--separator-color:var(--color-gray-3)}}.control{@mixin where-dark{--separator-color:var(--color-dark-4)}}.innerLabel{position:relative;z-index:2}
130
+ </style>
@@ -0,0 +1,46 @@
1
+ import type { AnyString, NuanceColor, NuanceRadius, NuanceSize } from '@nui/types';
2
+ import type { BoxProps } from './box.vue.js';
3
+ export interface SegmentedControlItem {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ export interface SegmentedControlProps extends BoxProps {
9
+ /** Items to render as controls */
10
+ data: (string | SegmentedControlItem)[];
11
+ /** Component size */
12
+ size?: NuanceSize | AnyString;
13
+ /** Border radius */
14
+ radius?: NuanceRadius;
15
+ /** Color from theme */
16
+ color?: NuanceColor;
17
+ /** Indicator `transition-duration` in ms */
18
+ transitionDuration?: number;
19
+ /** Indicator `transition-timing-function` */
20
+ transitionTimingFunction?: string;
21
+ /** Fills parent width */
22
+ fullWidth?: boolean;
23
+ /** Component orientation */
24
+ orientation?: 'horizontal' | 'vertical';
25
+ /** Disables the component */
26
+ disabled?: boolean;
27
+ /** Prevents value changes */
28
+ readOnly?: boolean;
29
+ /**
30
+ * Show borders between items
31
+ * @default `true`
32
+ */
33
+ withItemsBorders?: boolean;
34
+ }
35
+ type __VLS_Props = SegmentedControlProps;
36
+ type __VLS_ModelProps = {
37
+ modelValue?: string;
38
+ };
39
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
40
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
41
+ "update:modelValue": (value: string | undefined) => any;
42
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
43
+ "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
44
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
45
+ declare const _default: typeof __VLS_export;
46
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import type { CSSProperties } from 'vue';
2
+ import type { NuanceSpacing } from '../types/index.js';
3
+ import type { BoxProps } from './box.vue.js';
4
+ export interface StackProps extends BoxProps {
5
+ /** Key of `theme.spacing` or any valid CSS value to set `gap` property, numbers are converted to rem @default 'md' */
6
+ gap?: NuanceSpacing;
7
+ /** Controls `align-items` CSS property @default 'stretch' */
8
+ align?: CSSProperties['alignItems'];
9
+ /** Controls `justify-content` CSS property @default 'flex-start' */
10
+ justify?: CSSProperties['justifyContent'];
11
+ }
12
+ declare var __VLS_8: {};
13
+ type __VLS_Slots = {} & {
14
+ default?: (props: typeof __VLS_8) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<StackProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<StackProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,33 @@
1
+ <script setup>
2
+ import { getSpacing, useVarsResolver } from "#imports";
3
+ import Box from "./box.vue";
4
+ const {
5
+ gap,
6
+ align,
7
+ justify,
8
+ ...props
9
+ } = defineProps({
10
+ gap: { type: [String, Number], required: false },
11
+ align: { type: void 0, required: false },
12
+ justify: { type: void 0, required: false },
13
+ is: { type: null, required: false },
14
+ mod: { type: [Object, Array, null], required: false }
15
+ });
16
+ const style = useVarsResolver(() => ({
17
+ root: {
18
+ "--stack-gap": getSpacing(gap),
19
+ "--stack-align": align ?? void 0,
20
+ "--stack-justify": justify ?? void 0
21
+ }
22
+ }));
23
+ </script>
24
+
25
+ <template>
26
+ <Box v-bind='props' :style='style.root' :class='$style.root'>
27
+ <slot />
28
+ </Box>
29
+ </template>
30
+
31
+ <style module>
32
+ .root{align-items:var(--stack-align,stretch);display:flex;flex-direction:column;gap:var(--stack-gap,var(--spacing-md));justify-content:var(--stack-justify,flex-start)}
33
+ </style>
@@ -0,0 +1,24 @@
1
+ import type { CSSProperties } from 'vue';
2
+ import type { NuanceSpacing } from '../types/index.js';
3
+ import type { BoxProps } from './box.vue.js';
4
+ export interface StackProps extends BoxProps {
5
+ /** Key of `theme.spacing` or any valid CSS value to set `gap` property, numbers are converted to rem @default 'md' */
6
+ gap?: NuanceSpacing;
7
+ /** Controls `align-items` CSS property @default 'stretch' */
8
+ align?: CSSProperties['alignItems'];
9
+ /** Controls `justify-content` CSS property @default 'flex-start' */
10
+ justify?: CSSProperties['justifyContent'];
11
+ }
12
+ declare var __VLS_8: {};
13
+ type __VLS_Slots = {} & {
14
+ default?: (props: typeof __VLS_8) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<StackProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<StackProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };