@filipgutica/ui 0.1.0 → 0.2.1

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.
package/README.md CHANGED
@@ -22,6 +22,18 @@ The theme exposes Tailwind utilities such as `bg-ui-bg`, `bg-ui-surface`,
22
22
 
23
23
  ## Vue components
24
24
 
25
+ The first application-focused set includes:
26
+
27
+ - `UiButton` with primary, secondary, ghost, danger, and text variants
28
+ - `UiInput`, `UiSelect`, and `UiField` for consistent form controls
29
+ - `UiCheckbox`, `UiBadge`, `UiAlert`, and `UiProgress` for state and feedback
30
+ - `UiSurface` for restrained content grouping
31
+ - `UiDialog`, built on Reka UI, for accessible modal interaction
32
+
33
+ Place `UiInput` or `UiSelect` inside `UiField`. The field supplies the control ID
34
+ and automatically connects its description or error through the corresponding
35
+ ARIA relationship.
36
+
25
37
  ```vue
26
38
  <template>
27
39
  <UiButton variant="secondary" size="compact">
@@ -73,3 +85,14 @@ redistribute theme assets remain responsible for their licenses.
73
85
  pnpm install
74
86
  pnpm verify
75
87
  ```
88
+
89
+ Commits follow the Conventional Commits format. Releases run from `main`:
90
+
91
+ - `fix:` publishes a patch release
92
+ - `feat:` publishes a minor release
93
+ - a `BREAKING CHANGE:` footer publishes a major release
94
+
95
+ The `release.yml` workflow publishes through npm trusted publishing with GitHub
96
+ OIDC. Configure the npm package's trusted publisher once with user
97
+ `filipgutica`, repository `ui`, and workflow filename `release.yml`; no npm token
98
+ is stored in GitHub.
@@ -0,0 +1,18 @@
1
+ export interface UiAlertProps {
2
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'error';
3
+ }
4
+ declare var __VLS_8: {};
5
+ type __VLS_Slots = {} & {
6
+ default?: (props: typeof __VLS_8) => any;
7
+ };
8
+ declare const __VLS_base: import("vue").DefineComponent<UiAlertProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<UiAlertProps> & Readonly<{}>, {
9
+ tone: "neutral" | "info" | "success" | "warning" | "danger" | "error";
10
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
14
+ type __VLS_WithSlots<T, S> = T & {
15
+ new (): {
16
+ $slots: S;
17
+ };
18
+ };
@@ -0,0 +1,18 @@
1
+ export interface UiBadgeProps {
2
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
3
+ }
4
+ declare var __VLS_8: {};
5
+ type __VLS_Slots = {} & {
6
+ default?: (props: typeof __VLS_8) => any;
7
+ };
8
+ declare const __VLS_base: import("vue").DefineComponent<UiBadgeProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<UiBadgeProps> & Readonly<{}>, {
9
+ tone: "neutral" | "info" | "success" | "warning" | "danger";
10
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
14
+ type __VLS_WithSlots<T, S> = T & {
15
+ new (): {
16
+ $slots: S;
17
+ };
18
+ };
@@ -1,8 +1,9 @@
1
1
  export interface UiButtonProps {
2
2
  disabled?: boolean;
3
+ loading?: boolean;
3
4
  size?: 'default' | 'compact';
4
5
  type?: 'button' | 'submit' | 'reset';
5
- variant?: 'primary' | 'secondary' | 'text';
6
+ variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'text';
6
7
  }
7
8
  declare var __VLS_8: {};
8
9
  type __VLS_Slots = {} & {
@@ -10,9 +11,10 @@ type __VLS_Slots = {} & {
10
11
  };
11
12
  declare const __VLS_base: import("vue").DefineComponent<UiButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<UiButtonProps> & Readonly<{}>, {
12
13
  disabled: boolean;
14
+ loading: boolean;
13
15
  size: "default" | "compact";
14
16
  type: "button" | "submit" | "reset";
15
- variant: "primary" | "secondary" | "text";
17
+ variant: "primary" | "secondary" | "ghost" | "danger" | "text";
16
18
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
19
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
20
  declare const _default: typeof __VLS_export;
@@ -0,0 +1,24 @@
1
+ export interface UiCheckboxProps {
2
+ disabled?: boolean;
3
+ modelValue?: boolean;
4
+ }
5
+ declare var __VLS_15: {};
6
+ type __VLS_Slots = {} & {
7
+ default?: (props: typeof __VLS_15) => any;
8
+ };
9
+ declare const __VLS_base: import("vue").DefineComponent<UiCheckboxProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
10
+ "update:modelValue": (value: boolean) => any;
11
+ }, string, import("vue").PublicProps, Readonly<UiCheckboxProps> & Readonly<{
12
+ "onUpdate:modelValue"?: (value: boolean) => any;
13
+ }>, {
14
+ disabled: boolean;
15
+ modelValue: boolean;
16
+ }, {}, {}, {}, 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,24 @@
1
+ export interface UiDialogProps {
2
+ description?: string;
3
+ open: boolean;
4
+ title: string;
5
+ }
6
+ declare var __VLS_45: {}, __VLS_47: {};
7
+ type __VLS_Slots = {} & {
8
+ default?: (props: typeof __VLS_45) => any;
9
+ } & {
10
+ footer?: (props: typeof __VLS_47) => any;
11
+ };
12
+ declare const __VLS_base: import("vue").DefineComponent<UiDialogProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
13
+ "update:open": (value: boolean) => any;
14
+ }, string, import("vue").PublicProps, Readonly<UiDialogProps> & Readonly<{
15
+ "onUpdate:open"?: (value: boolean) => any;
16
+ }>, {}, {}, {}, {}, 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,21 @@
1
+ export interface UiFieldProps {
2
+ controlId: string;
3
+ description?: string;
4
+ error?: string;
5
+ label: string;
6
+ }
7
+ type __VLS_Slots = {
8
+ default(props: {
9
+ descriptionId: string | undefined;
10
+ errorId: string | undefined;
11
+ }): unknown;
12
+ };
13
+ declare const __VLS_base: import("vue").DefineComponent<UiFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<UiFieldProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
17
+ type __VLS_WithSlots<T, S> = T & {
18
+ new (): {
19
+ $slots: S;
20
+ };
21
+ };
@@ -0,0 +1,19 @@
1
+ export interface UiInputProps {
2
+ disabled?: boolean;
3
+ id?: string;
4
+ invalid?: boolean;
5
+ modelValue?: string;
6
+ type?: 'text' | 'search' | 'email' | 'password' | 'url';
7
+ }
8
+ declare const __VLS_export: import("vue").DefineComponent<UiInputProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
9
+ "update:modelValue": (value: string) => any;
10
+ }, string, import("vue").PublicProps, Readonly<UiInputProps> & Readonly<{
11
+ "onUpdate:modelValue"?: (value: string) => any;
12
+ }>, {
13
+ disabled: boolean;
14
+ type: "text" | "search" | "email" | "password" | "url";
15
+ modelValue: string;
16
+ invalid: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,8 @@
1
+ export interface UiProgressProps {
2
+ label: string;
3
+ max: number;
4
+ value: number | null;
5
+ }
6
+ declare const __VLS_export: import("vue").DefineComponent<UiProgressProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<UiProgressProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,27 @@
1
+ export interface UiSelectProps {
2
+ disabled?: boolean;
3
+ id?: string;
4
+ invalid?: boolean;
5
+ modelValue?: string;
6
+ }
7
+ declare var __VLS_10: {};
8
+ type __VLS_Slots = {} & {
9
+ default?: (props: typeof __VLS_10) => any;
10
+ };
11
+ declare const __VLS_base: import("vue").DefineComponent<UiSelectProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
12
+ "update:modelValue": (value: string) => any;
13
+ }, string, import("vue").PublicProps, Readonly<UiSelectProps> & Readonly<{
14
+ "onUpdate:modelValue"?: (value: string) => any;
15
+ }>, {
16
+ disabled: boolean;
17
+ modelValue: string;
18
+ invalid: boolean;
19
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
23
+ type __VLS_WithSlots<T, S> = T & {
24
+ new (): {
25
+ $slots: S;
26
+ };
27
+ };
@@ -0,0 +1,20 @@
1
+ export interface UiSurfaceProps {
2
+ as?: string;
3
+ padding?: 'none' | 'compact' | 'default';
4
+ }
5
+ declare var __VLS_8: {};
6
+ type __VLS_Slots = {} & {
7
+ default?: (props: typeof __VLS_8) => any;
8
+ };
9
+ declare const __VLS_base: import("vue").DefineComponent<UiSurfaceProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<UiSurfaceProps> & Readonly<{}>, {
10
+ as: string;
11
+ padding: "none" | "compact" | "default";
12
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
16
+ type __VLS_WithSlots<T, S> = T & {
17
+ new (): {
18
+ $slots: S;
19
+ };
20
+ };
@@ -0,0 +1,8 @@
1
+ import type { ComputedRef, InjectionKey } from 'vue';
2
+ export interface UiFieldContext {
3
+ controlId: ComputedRef<string>;
4
+ descriptionId: ComputedRef<string | undefined>;
5
+ errorId: ComputedRef<string | undefined>;
6
+ invalid: ComputedRef<boolean>;
7
+ }
8
+ export declare const uiFieldContextKey: InjectionKey<UiFieldContext>;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,20 @@
1
1
  export { default as UiButton } from './components/UiButton.vue';
2
2
  export type { UiButtonProps } from './components/UiButton.vue';
3
+ export { default as UiAlert } from './components/UiAlert.vue';
4
+ export type { UiAlertProps } from './components/UiAlert.vue';
5
+ export { default as UiBadge } from './components/UiBadge.vue';
6
+ export type { UiBadgeProps } from './components/UiBadge.vue';
7
+ export { default as UiCheckbox } from './components/UiCheckbox.vue';
8
+ export type { UiCheckboxProps } from './components/UiCheckbox.vue';
9
+ export { default as UiDialog } from './components/UiDialog.vue';
10
+ export type { UiDialogProps } from './components/UiDialog.vue';
11
+ export { default as UiField } from './components/UiField.vue';
12
+ export type { UiFieldProps } from './components/UiField.vue';
13
+ export { default as UiInput } from './components/UiInput.vue';
14
+ export type { UiInputProps } from './components/UiInput.vue';
15
+ export { default as UiProgress } from './components/UiProgress.vue';
16
+ export type { UiProgressProps } from './components/UiProgress.vue';
17
+ export { default as UiSelect } from './components/UiSelect.vue';
18
+ export type { UiSelectProps } from './components/UiSelect.vue';
19
+ export { default as UiSurface } from './components/UiSurface.vue';
20
+ export type { UiSurfaceProps } from './components/UiSurface.vue';
package/dist/index.js CHANGED
@@ -1,36 +1,316 @@
1
- import { createBlock as e, defineComponent as t, openBlock as n, renderSlot as r, unref as i, withCtx as a } from "vue";
2
- import { Primitive as o } from "reka-ui";
1
+ import { computed as e, createBlock as t, createCommentVNode as n, createElementBlock as r, createElementVNode as i, createTextVNode as a, createVNode as o, defineComponent as s, inject as c, normalizeStyle as l, openBlock as u, provide as d, renderSlot as f, toDisplayString as p, unref as m, withCtx as h } from "vue";
2
+ import { CheckboxIndicator as g, CheckboxRoot as _, DialogClose as v, DialogContent as y, DialogDescription as b, DialogOverlay as x, DialogPortal as S, DialogRoot as C, DialogTitle as w, Primitive as T, ProgressIndicator as E, ProgressRoot as D } from "reka-ui";
3
3
  //#endregion
4
4
  //#region src/components/UiButton.vue
5
- var s = /* @__PURE__ */ t({
5
+ var O = /* @__PURE__ */ s({
6
6
  __name: "UiButton",
7
7
  props: {
8
8
  disabled: {
9
9
  type: Boolean,
10
10
  default: !1
11
11
  },
12
+ loading: {
13
+ type: Boolean,
14
+ default: !1
15
+ },
12
16
  size: { default: "default" },
13
17
  type: { default: "button" },
14
18
  variant: { default: "primary" }
15
19
  },
16
- setup(t) {
17
- return (s, c) => (n(), e(i(o), {
20
+ setup(e) {
21
+ return (n, r) => (u(), t(m(T), {
18
22
  as: "button",
19
23
  class: "fg-button",
20
- "data-size": t.size,
21
- "data-variant": t.variant,
22
- disabled: t.disabled,
23
- type: t.type
24
+ "data-size": e.size,
25
+ "data-variant": e.variant,
26
+ "aria-busy": e.loading ? "true" : void 0,
27
+ disabled: e.disabled || e.loading,
28
+ type: e.type
24
29
  }, {
25
- default: a(() => [r(s.$slots, "default")]),
30
+ default: h(() => [f(n.$slots, "default")]),
26
31
  _: 3
27
32
  }, 8, [
28
33
  "data-size",
29
34
  "data-variant",
35
+ "aria-busy",
30
36
  "disabled",
31
37
  "type"
32
38
  ]));
33
39
  }
40
+ }), k = /* @__PURE__ */ s({
41
+ __name: "UiAlert",
42
+ props: { tone: { default: "neutral" } },
43
+ setup(e) {
44
+ return (n, r) => (u(), t(m(T), {
45
+ as: "div",
46
+ class: "fg-alert",
47
+ "data-tone": e.tone,
48
+ role: e.tone === "danger" || e.tone === "error" ? "alert" : "status"
49
+ }, {
50
+ default: h(() => [f(n.$slots, "default")]),
51
+ _: 3
52
+ }, 8, ["data-tone", "role"]));
53
+ }
54
+ }), A = /* @__PURE__ */ s({
55
+ __name: "UiBadge",
56
+ props: { tone: { default: "neutral" } },
57
+ setup(e) {
58
+ return (n, r) => (u(), t(m(T), {
59
+ as: "span",
60
+ class: "fg-badge",
61
+ "data-tone": e.tone
62
+ }, {
63
+ default: h(() => [f(n.$slots, "default")]),
64
+ _: 3
65
+ }, 8, ["data-tone"]));
66
+ }
67
+ }), j = ["data-disabled"], M = { class: "fg-checkbox__label" }, N = /* @__PURE__ */ s({
68
+ __name: "UiCheckbox",
69
+ props: {
70
+ disabled: {
71
+ type: Boolean,
72
+ default: !1
73
+ },
74
+ modelValue: {
75
+ type: Boolean,
76
+ default: !1
77
+ }
78
+ },
79
+ emits: ["update:modelValue"],
80
+ setup(e, { emit: t }) {
81
+ let n = t, s = (e) => {
82
+ n("update:modelValue", e === !0);
83
+ };
84
+ return (t, n) => (u(), r("label", {
85
+ class: "fg-checkbox",
86
+ "data-disabled": e.disabled ? "true" : void 0
87
+ }, [o(m(_), {
88
+ class: "fg-checkbox__control",
89
+ disabled: e.disabled,
90
+ "model-value": e.modelValue,
91
+ "onUpdate:modelValue": s
92
+ }, {
93
+ default: h(() => [o(m(g), { class: "fg-checkbox__indicator" }, {
94
+ default: h(() => [...n[0] ||= [a("✓", -1)]]),
95
+ _: 1
96
+ })]),
97
+ _: 1
98
+ }, 8, ["disabled", "model-value"]), i("span", M, [f(t.$slots, "default")])], 8, j));
99
+ }
100
+ }), P = { class: "fg-dialog__header" }, F = { class: "fg-dialog__body" }, I = {
101
+ key: 0,
102
+ class: "fg-dialog__footer"
103
+ }, L = /* @__PURE__ */ s({
104
+ __name: "UiDialog",
105
+ props: {
106
+ description: {},
107
+ open: { type: Boolean },
108
+ title: {}
109
+ },
110
+ emits: ["update:open"],
111
+ setup(e, { emit: s }) {
112
+ let c = s;
113
+ return (s, l) => (u(), t(m(C), {
114
+ open: e.open,
115
+ "onUpdate:open": l[0] ||= (e) => c("update:open", e)
116
+ }, {
117
+ default: h(() => [o(m(S), null, {
118
+ default: h(() => [o(m(x), { class: "fg-dialog__overlay" }), o(m(y), {
119
+ class: "fg-dialog__content",
120
+ "aria-modal": "true"
121
+ }, {
122
+ default: h(() => [
123
+ i("div", P, [i("div", null, [o(m(w), { class: "fg-dialog__title" }, {
124
+ default: h(() => [a(p(e.title), 1)]),
125
+ _: 1
126
+ }), e.description ? (u(), t(m(b), {
127
+ key: 0,
128
+ class: "fg-dialog__description"
129
+ }, {
130
+ default: h(() => [a(p(e.description), 1)]),
131
+ _: 1
132
+ })) : n("", !0)]), o(m(v), {
133
+ class: "fg-dialog__close",
134
+ "aria-label": "Close dialog"
135
+ }, {
136
+ default: h(() => [...l[1] ||= [a("×", -1)]]),
137
+ _: 1
138
+ })]),
139
+ i("div", F, [f(s.$slots, "default")]),
140
+ s.$slots.footer ? (u(), r("div", I, [f(s.$slots, "footer")])) : n("", !0)
141
+ ]),
142
+ _: 3
143
+ })]),
144
+ _: 3
145
+ })]),
146
+ _: 3
147
+ }, 8, ["open"]));
148
+ }
149
+ }), R = Symbol("UiFieldContext"), z = ["data-invalid"], B = ["for"], V = ["id"], H = ["id"], U = /* @__PURE__ */ s({
150
+ __name: "UiField",
151
+ props: {
152
+ controlId: {},
153
+ description: {},
154
+ error: {},
155
+ label: {}
156
+ },
157
+ setup(t) {
158
+ let a = t, o = e(() => a.description && !a.error ? `${a.controlId}-description` : void 0), s = e(() => a.error ? `${a.controlId}-error` : void 0), c = e(() => !!a.error);
159
+ return d(R, {
160
+ controlId: e(() => a.controlId),
161
+ descriptionId: o,
162
+ errorId: s,
163
+ invalid: c
164
+ }), (e, a) => (u(), r("div", {
165
+ class: "fg-field",
166
+ "data-invalid": t.error ? "true" : void 0
167
+ }, [
168
+ i("label", {
169
+ class: "fg-field__label",
170
+ for: t.controlId
171
+ }, p(t.label), 9, B),
172
+ f(e.$slots, "default", {
173
+ descriptionId: o.value,
174
+ errorId: s.value
175
+ }),
176
+ t.description && !t.error ? (u(), r("p", {
177
+ key: 0,
178
+ id: o.value,
179
+ class: "fg-field__description",
180
+ "data-field-description": ""
181
+ }, p(t.description), 9, V)) : n("", !0),
182
+ t.error ? (u(), r("p", {
183
+ key: 1,
184
+ id: s.value,
185
+ class: "fg-field__error",
186
+ "data-field-error": ""
187
+ }, p(t.error), 9, H)) : n("", !0)
188
+ ], 8, z));
189
+ }
190
+ }), W = /* @__PURE__ */ s({
191
+ __name: "UiInput",
192
+ props: {
193
+ disabled: {
194
+ type: Boolean,
195
+ default: !1
196
+ },
197
+ id: {},
198
+ invalid: {
199
+ type: Boolean,
200
+ default: !1
201
+ },
202
+ modelValue: { default: "" },
203
+ type: { default: "text" }
204
+ },
205
+ emits: ["update:modelValue"],
206
+ setup(e, { emit: n }) {
207
+ let r = c(R, void 0), i = n, a = (e) => {
208
+ e.currentTarget instanceof HTMLInputElement && i("update:modelValue", e.currentTarget.value);
209
+ };
210
+ return (n, i) => (u(), t(m(T), {
211
+ as: "input",
212
+ class: "fg-input",
213
+ id: e.id ?? m(r)?.controlId.value,
214
+ "aria-describedby": m(r)?.descriptionId.value,
215
+ "aria-errormessage": m(r)?.errorId.value,
216
+ "aria-invalid": e.invalid || m(r)?.invalid.value ? "true" : void 0,
217
+ disabled: e.disabled,
218
+ type: e.type,
219
+ value: e.modelValue,
220
+ onInput: a
221
+ }, null, 8, [
222
+ "id",
223
+ "aria-describedby",
224
+ "aria-errormessage",
225
+ "aria-invalid",
226
+ "disabled",
227
+ "type",
228
+ "value"
229
+ ]));
230
+ }
231
+ }), G = /* @__PURE__ */ s({
232
+ __name: "UiProgress",
233
+ props: {
234
+ label: {},
235
+ max: {},
236
+ value: {}
237
+ },
238
+ setup(n) {
239
+ let r = n, i = e(() => r.value === null || r.max <= 0 ? 0 : Math.min(100, Math.max(0, r.value / r.max * 100)));
240
+ return (e, r) => (u(), t(m(D), {
241
+ class: "fg-progress",
242
+ "aria-label": n.label,
243
+ max: n.max,
244
+ "model-value": n.value
245
+ }, {
246
+ default: h(() => [o(m(E), {
247
+ class: "fg-progress__indicator",
248
+ style: l({ transform: `translateX(-${100 - i.value}%)` })
249
+ }, null, 8, ["style"])]),
250
+ _: 1
251
+ }, 8, [
252
+ "aria-label",
253
+ "max",
254
+ "model-value"
255
+ ]));
256
+ }
257
+ }), K = /* @__PURE__ */ s({
258
+ __name: "UiSelect",
259
+ props: {
260
+ disabled: {
261
+ type: Boolean,
262
+ default: !1
263
+ },
264
+ id: {},
265
+ invalid: {
266
+ type: Boolean,
267
+ default: !1
268
+ },
269
+ modelValue: { default: "" }
270
+ },
271
+ emits: ["update:modelValue"],
272
+ setup(e, { emit: n }) {
273
+ let r = c(R, void 0), i = n, a = (e) => {
274
+ e.currentTarget instanceof HTMLSelectElement && i("update:modelValue", e.currentTarget.value);
275
+ };
276
+ return (n, i) => (u(), t(m(T), {
277
+ as: "select",
278
+ class: "fg-select",
279
+ id: e.id ?? m(r)?.controlId.value,
280
+ "aria-describedby": m(r)?.descriptionId.value,
281
+ "aria-errormessage": m(r)?.errorId.value,
282
+ "aria-invalid": e.invalid || m(r)?.invalid.value ? "true" : void 0,
283
+ disabled: e.disabled,
284
+ value: e.modelValue,
285
+ onChange: a
286
+ }, {
287
+ default: h(() => [f(n.$slots, "default")]),
288
+ _: 3
289
+ }, 8, [
290
+ "id",
291
+ "aria-describedby",
292
+ "aria-errormessage",
293
+ "aria-invalid",
294
+ "disabled",
295
+ "value"
296
+ ]));
297
+ }
298
+ }), q = /* @__PURE__ */ s({
299
+ __name: "UiSurface",
300
+ props: {
301
+ as: { default: "section" },
302
+ padding: { default: "default" }
303
+ },
304
+ setup(e) {
305
+ return (n, r) => (u(), t(m(T), {
306
+ as: e.as,
307
+ class: "fg-surface",
308
+ "data-padding": e.padding
309
+ }, {
310
+ default: h(() => [f(n.$slots, "default")]),
311
+ _: 3
312
+ }, 8, ["as", "data-padding"]));
313
+ }
34
314
  });
35
315
  //#endregion
36
- export { s as UiButton };
316
+ export { k as UiAlert, A as UiBadge, O as UiButton, N as UiCheckbox, L as UiDialog, U as UiField, W as UiInput, G as UiProgress, K as UiSelect, q as UiSurface };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filipgutica/ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "A compact Vue design system with Tailwind CSS and VS Code theme support",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -32,16 +32,19 @@
32
32
  "scripts": {
33
33
  "build": "vite build && vue-tsc -p tsconfig.build.json --emitDeclarationOnly",
34
34
  "prepublishOnly": "pnpm verify",
35
+ "release": "semantic-release",
36
+ "release:dry-run": "semantic-release --dry-run --no-ci",
35
37
  "test": "vitest run",
36
38
  "typecheck": "vue-tsc -p tsconfig.json --noEmit",
37
- "verify": "pnpm typecheck && pnpm test && pnpm build && pnpm pack --dry-run"
39
+ "verify": "pnpm typecheck && pnpm test && pnpm build && pnpm pack --dry-run && publint"
38
40
  },
39
41
  "engines": {
40
42
  "node": ">=22.12"
41
43
  },
42
44
  "packageManager": "pnpm@11.6.0",
43
45
  "publishConfig": {
44
- "access": "public"
46
+ "access": "public",
47
+ "provenance": true
45
48
  },
46
49
  "license": "MIT",
47
50
  "repository": {
@@ -61,10 +64,16 @@
61
64
  "vue": ">=3.4.0"
62
65
  },
63
66
  "devDependencies": {
67
+ "@commitlint/cli": "^21.2.2",
68
+ "@commitlint/config-conventional": "^21.2.2",
69
+ "@semantic-release/commit-analyzer": "13.0.1",
64
70
  "@types/node": "^26.0.0",
65
71
  "@vitejs/plugin-vue": "^6.0.0",
66
72
  "@vue/test-utils": "^2.4.6",
73
+ "conventional-changelog-conventionalcommits": "^9.3.1",
67
74
  "happy-dom": "^20.12.2",
75
+ "publint": "^0.3.24",
76
+ "semantic-release": "^25.0.9",
68
77
  "tailwindcss": "^4.3.3",
69
78
  "typescript": "^5.9.3",
70
79
  "vite": "^8.2.2",
@@ -1,16 +1,24 @@
1
1
  @layer components {
2
+ .fg-button,
3
+ .fg-input,
4
+ .fg-select,
5
+ .fg-dialog__close {
6
+ min-height: 2.5rem;
7
+ border: 1px solid transparent;
8
+ border-radius: var(--radius-control);
9
+ font: inherit;
10
+ }
11
+
2
12
  .fg-button {
3
13
  display: inline-flex;
4
14
  align-items: center;
5
15
  justify-content: center;
6
16
  gap: 0.5rem;
7
- border: 1px solid transparent;
8
- border-radius: var(--radius-control);
9
- padding: 0.65rem 0.9rem;
17
+ padding: 0.55rem 0.85rem;
10
18
  color: var(--color-button-text);
11
19
  background: var(--color-button-bg);
12
- font: inherit;
13
- font-weight: 650;
20
+ font-weight: 680;
21
+ line-height: 1.25;
14
22
  cursor: pointer;
15
23
  }
16
24
 
@@ -18,29 +26,49 @@
18
26
  background: var(--color-button-hover);
19
27
  }
20
28
 
21
- .fg-button:focus-visible {
22
- outline: 3px solid var(--color-focus);
23
- outline-offset: 2px;
29
+ .fg-button:disabled,
30
+ .fg-input:disabled,
31
+ .fg-select:disabled,
32
+ .fg-checkbox[data-disabled="true"] {
33
+ cursor: not-allowed;
34
+ opacity: 0.58;
24
35
  }
25
36
 
26
- .fg-button:disabled {
37
+ .fg-button[aria-busy="true"] {
27
38
  cursor: wait;
28
- opacity: 0.6;
29
39
  }
30
40
 
31
41
  .fg-button[data-size="compact"] {
32
- padding: 0.45rem 0.65rem;
33
- font-size: 0.78rem;
42
+ min-height: 2rem;
43
+ padding: 0.35rem 0.6rem;
44
+ font-size: 0.8125rem;
34
45
  }
35
46
 
36
47
  .fg-button[data-variant="secondary"] {
37
48
  border-color: var(--color-border);
38
49
  color: var(--color-text);
39
- background: var(--color-surface);
50
+ background: var(--color-surface-raised);
51
+ }
52
+
53
+ .fg-button[data-variant="secondary"]:hover:not(:disabled),
54
+ .fg-button[data-variant="ghost"]:hover:not(:disabled) {
55
+ color: var(--color-text);
56
+ background: var(--color-row-hover);
57
+ }
58
+
59
+ .fg-button[data-variant="ghost"] {
60
+ color: var(--color-muted);
61
+ background: transparent;
62
+ }
63
+
64
+ .fg-button[data-variant="danger"] {
65
+ color: var(--color-error);
66
+ background: var(--color-error-surface);
40
67
  }
41
68
 
42
69
  .fg-button[data-variant="text"] {
43
- padding-inline: 0;
70
+ min-height: auto;
71
+ padding: 0;
44
72
  color: var(--color-link);
45
73
  background: transparent;
46
74
  text-decoration: underline;
@@ -52,8 +80,248 @@
52
80
  background: transparent;
53
81
  }
54
82
 
55
- :root.high-contrast .fg-button {
83
+ .fg-input,
84
+ .fg-select {
85
+ width: 100%;
86
+ padding: 0.5rem 0.7rem;
87
+ border-color: var(--color-input-border);
88
+ color: var(--color-input-text);
89
+ background: var(--color-input-bg);
90
+ line-height: 1.35;
91
+ }
92
+
93
+ .fg-input::placeholder {
94
+ color: var(--color-input-placeholder);
95
+ opacity: 1;
96
+ }
97
+
98
+ .fg-input[aria-invalid="true"],
99
+ .fg-select[aria-invalid="true"] {
100
+ border-color: var(--color-error);
101
+ }
102
+
103
+ .fg-field {
104
+ display: grid;
105
+ gap: 0.4rem;
106
+ min-width: 0;
107
+ }
108
+
109
+ .fg-field__label {
110
+ color: var(--color-muted);
111
+ font-size: 0.8125rem;
112
+ font-weight: 680;
113
+ }
114
+
115
+ .fg-field__description,
116
+ .fg-field__error {
117
+ margin: 0;
118
+ font-size: 0.8125rem;
119
+ }
120
+
121
+ .fg-field__description { color: var(--color-muted); }
122
+ .fg-field__error { color: var(--color-error); }
123
+
124
+ .fg-checkbox {
125
+ display: inline-flex;
126
+ align-items: flex-start;
127
+ gap: 0.65rem;
128
+ color: var(--color-text);
129
+ cursor: pointer;
130
+ }
131
+
132
+ .fg-checkbox__control {
133
+ display: inline-grid;
134
+ flex: none;
135
+ width: 1.15rem;
136
+ height: 1.15rem;
137
+ margin-top: 0.1rem;
138
+ place-items: center;
139
+ border: 1px solid var(--color-input-border);
140
+ border-radius: 0.3rem;
141
+ color: var(--color-button-text);
142
+ background: var(--color-input-bg);
143
+ }
144
+
145
+ .fg-checkbox__control[data-state="checked"] {
146
+ border-color: var(--color-button-bg);
147
+ background: var(--color-button-bg);
148
+ }
149
+
150
+ .fg-checkbox__indicator {
151
+ font-size: 0.8rem;
152
+ font-weight: 800;
153
+ line-height: 1;
154
+ }
155
+
156
+ .fg-checkbox__label { line-height: 1.4; }
157
+
158
+ .fg-badge {
159
+ display: inline-flex;
160
+ align-items: center;
161
+ width: fit-content;
162
+ border: 1px solid var(--color-border-subtle);
163
+ border-radius: var(--radius-pill);
164
+ padding: 0.18rem 0.48rem;
165
+ color: var(--color-muted);
166
+ background: var(--color-surface-muted);
167
+ font-size: 0.75rem;
168
+ font-weight: 700;
169
+ line-height: 1.25;
170
+ }
171
+
172
+ .fg-badge[data-tone="info"] { color: var(--color-info); background: var(--color-info-surface); }
173
+ .fg-badge[data-tone="success"] { color: var(--color-success); background: var(--color-success-surface); }
174
+ .fg-badge[data-tone="warning"] { color: var(--color-warning); background: var(--color-warning-surface); }
175
+ .fg-badge[data-tone="danger"] { color: var(--color-error); background: var(--color-error-surface); }
176
+
177
+ .fg-alert {
178
+ display: flex;
179
+ align-items: flex-start;
180
+ gap: 0.75rem;
181
+ padding: 0.85rem 1rem;
182
+ border: 1px solid var(--color-border-subtle);
183
+ border-radius: var(--radius-control);
184
+ color: var(--color-text);
185
+ background: var(--color-surface);
186
+ }
187
+
188
+ .fg-alert[data-tone="info"] { background: var(--color-info-surface); }
189
+ .fg-alert[data-tone="success"] { background: var(--color-success-surface); }
190
+ .fg-alert[data-tone="warning"] { background: var(--color-warning-surface); }
191
+ .fg-alert[data-tone="danger"],
192
+ .fg-alert[data-tone="error"] {
193
+ border-color: var(--color-error);
194
+ color: var(--color-error);
195
+ background: var(--color-error-surface);
196
+ }
197
+
198
+ .fg-surface {
199
+ border: 1px solid var(--color-border-subtle);
200
+ border-radius: var(--radius-panel);
201
+ background: var(--color-surface-raised);
202
+ }
203
+
204
+ .fg-surface[data-padding="compact"] { padding: 0.85rem; }
205
+ .fg-surface[data-padding="default"] { padding: 1.2rem; }
206
+
207
+ .fg-progress {
208
+ position: relative;
209
+ height: 0.55rem;
210
+ overflow: hidden;
211
+ border-radius: var(--radius-pill);
212
+ background: var(--color-surface-muted);
213
+ }
214
+
215
+ .fg-progress__indicator {
216
+ width: 100%;
217
+ height: 100%;
218
+ border-radius: inherit;
219
+ background: var(--color-progress);
220
+ transition: transform var(--duration-fast) ease-out;
221
+ }
222
+
223
+ .fg-dialog__overlay {
224
+ position: fixed;
225
+ inset: 0;
226
+ z-index: 50;
227
+ background: var(--color-overlay);
228
+ }
229
+
230
+ .fg-dialog__content {
231
+ position: fixed;
232
+ z-index: 51;
233
+ top: 50%;
234
+ left: 50%;
235
+ display: grid;
236
+ width: min(42rem, calc(100vw - 2rem));
237
+ max-height: min(42rem, calc(100vh - 2rem));
238
+ overflow: hidden;
239
+ transform: translate(-50%, -50%);
240
+ border: 1px solid var(--color-border);
241
+ border-radius: var(--radius-panel);
242
+ color: var(--color-text);
243
+ background: var(--color-surface-raised);
244
+ box-shadow: var(--shadow-dialog);
245
+ }
246
+
247
+ .fg-dialog__header,
248
+ .fg-dialog__footer {
249
+ display: flex;
250
+ align-items: flex-start;
251
+ justify-content: space-between;
252
+ gap: 1rem;
253
+ padding: 1rem 1.2rem;
254
+ }
255
+
256
+ .fg-dialog__header { border-bottom: 1px solid var(--color-border-subtle); }
257
+ .fg-dialog__footer { justify-content: flex-end; border-top: 1px solid var(--color-border-subtle); }
258
+
259
+ .fg-dialog__title {
260
+ margin: 0;
261
+ font-size: 1.1rem;
262
+ font-weight: 750;
263
+ }
264
+
265
+ .fg-dialog__description {
266
+ margin: 0.25rem 0 0;
267
+ color: var(--color-muted);
268
+ font-size: 0.875rem;
269
+ }
270
+
271
+ .fg-dialog__body {
272
+ min-height: 0;
273
+ overflow: auto;
274
+ padding: 1.2rem;
275
+ }
276
+
277
+ .fg-dialog__close {
278
+ display: inline-grid;
279
+ flex: none;
280
+ width: 2.5rem;
281
+ padding: 0;
282
+ place-items: center;
283
+ color: var(--color-muted);
284
+ background: transparent;
285
+ font-size: 1.35rem;
286
+ cursor: pointer;
287
+ }
288
+
289
+ .fg-dialog__close:hover { color: var(--color-text); background: var(--color-row-hover); }
290
+
291
+ .fg-button:focus-visible,
292
+ .fg-input:focus-visible,
293
+ .fg-select:focus-visible,
294
+ .fg-checkbox__control:focus-visible,
295
+ .fg-dialog__close:focus-visible,
296
+ .fg-dialog__content:focus-visible {
297
+ outline: 3px solid var(--color-focus);
298
+ outline-offset: 2px;
299
+ }
300
+
301
+ :root.high-contrast .fg-button,
302
+ :root.high-contrast .fg-input,
303
+ :root.high-contrast .fg-select,
304
+ :root.high-contrast .fg-checkbox__control,
305
+ :root.high-contrast .fg-alert,
306
+ :root.high-contrast .fg-surface,
307
+ :root.high-contrast .fg-dialog__content {
56
308
  border-width: 2px;
57
309
  border-color: var(--color-border);
58
310
  }
311
+
312
+ @media (max-width: 640px) {
313
+ .fg-dialog__content {
314
+ top: auto;
315
+ bottom: 0;
316
+ left: 0;
317
+ width: 100%;
318
+ max-height: calc(100vh - 1rem);
319
+ transform: none;
320
+ border-radius: var(--radius-panel) var(--radius-panel) 0 0;
321
+ }
322
+ }
323
+
324
+ @media (prefers-reduced-motion: reduce) {
325
+ .fg-progress__indicator { transition: none; }
326
+ }
59
327
  }
package/src/theme.css CHANGED
@@ -37,8 +37,10 @@
37
37
  --color-success-surface: color-mix(in srgb, var(--color-success) 12%, var(--color-surface));
38
38
  --color-info-surface: color-mix(in srgb, var(--color-info) 12%, var(--color-surface));
39
39
  --radius-control: 0.55rem;
40
- --radius-panel: 0.9rem;
40
+ --radius-panel: 0.75rem;
41
+ --radius-pill: 999px;
41
42
  --duration-fast: 180ms;
43
+ --shadow-dialog: 0 20px 64px rgb(0 0 0 / 24%);
42
44
  }
43
45
 
44
46
  :root.dark {