@baklavue/composables 1.0.0-preview.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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "semantic-release-monorepo",
3
+ "tagFormat": "@baklavue/composables-v${version}",
4
+ "branches": ["main", { "name": "preview", "prerelease": true, "channel": "beta" }],
5
+ "repositoryUrl": "https://github.com/erbilnas/baklavue",
6
+ "plugins": [
7
+ "@semantic-release/commit-analyzer",
8
+ "@semantic-release/release-notes-generator",
9
+ "@semantic-release/changelog",
10
+ ["@semantic-release/npm", { "npmPublish": true }],
11
+ "@semantic-release/github",
12
+ "@semantic-release/git"
13
+ ]
14
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # @baklavue/composables-v1.0.0-preview.1 (2026-02-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fix package version ([620618d](https://github.com/erbilnas/baklavue/commit/620618df664022f1b68859aee4b374f6586731bb))
7
+ * fix release ci ([6c22554](https://github.com/erbilnas/baklavue/commit/6c225541b182c4b8692af000e7c13ca6e546599a))
8
+ * fix release ci ([6e23d99](https://github.com/erbilnas/baklavue/commit/6e23d99cac122d033c72d6d98819e0329c92bcdc))
9
+
10
+
11
+ ### Features
12
+
13
+ * add accordion and alert components ([10b8155](https://github.com/erbilnas/baklavue/commit/10b81556184432deb2772d3489c24be6115e667c))
14
+ * add info for npm package ([8ffbb9f](https://github.com/erbilnas/baklavue/commit/8ffbb9f81d108d633608ebe8d3854a2f6861475a))
15
+ * add localization guide ([0367e41](https://github.com/erbilnas/baklavue/commit/0367e41741cdffb2dfb7ec631db3cb47b12fe6cc))
16
+ * add new components ([ec926f0](https://github.com/erbilnas/baklavue/commit/ec926f0e9ba11c1a28a97c8afee4829b38ed8539))
17
+ * add theme customizer tool for docs ([70c08cf](https://github.com/erbilnas/baklavue/commit/70c08cf505ff02b1ab6c3e569055b703674bb822))
18
+ * disable npm package-lock for bun ([d91c5ca](https://github.com/erbilnas/baklavue/commit/d91c5ca0d8566aff9c021b1cad431eda86bc2d95))
19
+ * disable npm package-lock for bun ([86fd15e](https://github.com/erbilnas/baklavue/commit/86fd15ed42ded4e198ed22845acdb659cdfde191))
20
+ * initial commit ([d98b3f9](https://github.com/erbilnas/baklavue/commit/d98b3f9e6cc6e8238f197eaa117a050365987b18))
21
+ * seperate publishing packages ([e8092d8](https://github.com/erbilnas/baklavue/commit/e8092d87a6579cf6d2798053cd6150fc4ccd98a8))
22
+
23
+ # [1.0.0-preview.5](https://github.com/erbilnas/baklavue/compare/v1.0.0-preview.4...v1.0.0-preview.5) (2026-02-09)
24
+
25
+
26
+ ### Features
27
+
28
+ * add github pages support ([c76dffa](https://github.com/erbilnas/baklavue/commit/c76dffa8eb32b8293aca7c7e1f5d2ee4139802b0))
29
+
30
+ # [1.0.0-preview.3](https://github.com/erbilnas/baklavue/compare/v1.0.0-preview.2...v1.0.0-preview.3) (2026-02-09)
31
+
32
+
33
+ ### Features
34
+
35
+ * add new components ([22a1506](https://github.com/erbilnas/baklavue/commit/22a150681ad8587c92ad5546fbc256d9275b6d00))
36
+
37
+ # [1.0.0-preview.2](https://github.com/erbilnas/baklavue/compare/v1.0.0-preview.1...v1.0.0-preview.2) (2026-02-09)
38
+
39
+
40
+ ### Features
41
+
42
+ * add accordion, alert and badge components ([b939941](https://github.com/erbilnas/baklavue/commit/b939941e469a29b10ddcc6196f97700951672f97))
43
+
44
+ # 1.0.0-preview.1 (2026-02-09)
45
+
46
+
47
+ ### Bug Fixes
48
+
49
+ * fix known issue of npm@11 ([f73a8fa](https://github.com/erbilnas/baklavue/commit/f73a8fa56191c083545b0f02d0672539a8dfbfab))
50
+ * fix known issue of npm@11 ([6e90aeb](https://github.com/erbilnas/baklavue/commit/6e90aeb37bbd42c9d0e524735ec34481f0c87ecf))
51
+ * fix package version ([620618d](https://github.com/erbilnas/baklavue/commit/620618df664022f1b68859aee4b374f6586731bb))
52
+ * fix release ci ([6c22554](https://github.com/erbilnas/baklavue/commit/6c225541b182c4b8692af000e7c13ca6e546599a))
53
+ * fix release ci ([6e23d99](https://github.com/erbilnas/baklavue/commit/6e23d99cac122d033c72d6d98819e0329c92bcdc))
54
+
55
+
56
+ ### Features
57
+
58
+ * add info for npm package ([8ffbb9f](https://github.com/erbilnas/baklavue/commit/8ffbb9f81d108d633608ebe8d3854a2f6861475a))
59
+ * add new feature ([771acd1](https://github.com/erbilnas/baklavue/commit/771acd19bbd730e4110f61d2df06a5f6a958a3a7))
60
+ * disable npm package-lock for bun ([d91c5ca](https://github.com/erbilnas/baklavue/commit/d91c5ca0d8566aff9c021b1cad431eda86bc2d95))
61
+ * disable npm package-lock for bun ([86fd15e](https://github.com/erbilnas/baklavue/commit/86fd15ed42ded4e198ed22845acdb659cdfde191))
62
+ * disable npm package-lock for bun ([ca4503d](https://github.com/erbilnas/baklavue/commit/ca4503d1304cacb6212c6b328426d7c51152f893))
63
+ * increase semantic release version\ ([b510944](https://github.com/erbilnas/baklavue/commit/b510944562238e02fa6ce34c1b10540e11d47f16))
64
+ * initial commit ([d98b3f9](https://github.com/erbilnas/baklavue/commit/d98b3f9e6cc6e8238f197eaa117a050365987b18))
package/README.md ADDED
@@ -0,0 +1,276 @@
1
+ # @baklavue/composables
2
+
3
+ A collection of Vue 3 composables for seamless integration with Baklava UI components. This package provides convenient utilities and hooks to enhance your Vue applications with Baklava's design system.
4
+
5
+ ## 🚀 Features
6
+
7
+ - **Vue 3 Composition API**: Built with modern Vue 3 composables
8
+ - **TypeScript Support**: Full TypeScript support with proper type definitions
9
+ - **Baklava Integration**: Seamlessly works with Baklava UI components
10
+ - **Lightweight**: Minimal bundle size with no unnecessary dependencies
11
+ - **Tree-shakable**: Only import what you need
12
+
13
+ ## 📦 Installation
14
+
15
+ ```bash
16
+ # Using npm
17
+ npm install @baklavue/composables
18
+
19
+ # Using yarn
20
+ yarn add @baklavue/composables
21
+
22
+ # Using pnpm
23
+ pnpm add @baklavue/composables
24
+
25
+ # Using bun
26
+ bun add @baklavue/composables
27
+ ```
28
+
29
+ ## 🔧 Prerequisites
30
+
31
+ - Vue 3.x
32
+ - TypeScript 5.9.2+ (peer dependency)
33
+ - Baklava UI components installed in your project
34
+
35
+ ## 📚 Available Composables
36
+
37
+ ### `useCsv`
38
+
39
+ A composable for CSV parsing, creating, and downloading. Uses PapaParse for RFC 4180-compliant handling of quoted fields, commas in values, and edge cases.
40
+
41
+ #### Basic Usage
42
+
43
+ ```vue
44
+ <script setup lang="ts">
45
+ import { useCsv } from "@baklavue/composables";
46
+
47
+ const { parse, parseFile, create, download } = useCsv();
48
+
49
+ // Parse CSV string
50
+ const result = parse("name,age\nAlice,30\nBob,25", { header: true });
51
+ console.log(result.data); // [{ name: "Alice", age: "30" }, ...]
52
+
53
+ // Parse file (async)
54
+ const handleFileUpload = async (event: Event) => {
55
+ const file = (event.target as HTMLInputElement).files?.[0];
56
+ if (file) {
57
+ const fileResult = await parseFile(file, { header: true });
58
+ console.log(fileResult.data);
59
+ }
60
+ };
61
+
62
+ // Create CSV from array of objects
63
+ const csv = create(
64
+ [
65
+ { name: "Alice", age: 30 },
66
+ { name: "Bob", age: 25 },
67
+ ]
68
+ );
69
+
70
+ // Download CSV file
71
+ const exportData = () => {
72
+ download(
73
+ [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }],
74
+ "export.csv"
75
+ );
76
+ };
77
+ </script>
78
+ ```
79
+
80
+ #### API Reference
81
+
82
+ The `useCsv` composable returns an object with the following methods:
83
+
84
+ ##### `parse(csv: string, options?: CsvParseOptions)`
85
+
86
+ Parses a CSV string and returns `{ data, errors, meta }`. Synchronous.
87
+
88
+ ##### `parseFile(file: File, options?: CsvParseOptions)`
89
+
90
+ Parses a File or Blob asynchronously. Returns `Promise<ParseResult>`.
91
+
92
+ ##### `create(data: CsvData, options?: CsvCreateOptions)`
93
+
94
+ Creates a CSV string from an array of objects, array of arrays, or `{ fields, data }` object.
95
+
96
+ ##### `download(data: CsvData, filename?: string, options?: CsvCreateOptions)`
97
+
98
+ Creates a CSV string and triggers a browser download. Adds UTF-8 BOM for Excel compatibility.
99
+
100
+ #### Options
101
+
102
+ **CsvParseOptions:**
103
+
104
+ - `delimiter` – Delimiter character (default: auto-detect)
105
+ - `header` – If true, first row is header (returns array of objects)
106
+ - `dynamicTyping` – If true, numbers and booleans are converted
107
+ - `skipEmptyLines` – Skip empty lines (`true` or `'greedy'`)
108
+
109
+ **CsvCreateOptions:**
110
+
111
+ - `delimiter` – Delimiter character (default: comma)
112
+ - `header` – Include header row (default: true)
113
+ - `columns` – Column order for array of objects
114
+ - `escapeFormulae` – Escape leading `=`, `+`, `-`, `@` to prevent CSV injection
115
+
116
+ ### `useNotification`
117
+
118
+ A composable for managing Baklava notification system with a simple and intuitive API.
119
+
120
+ #### Basic Usage
121
+
122
+ ```vue
123
+ <template>
124
+ <div>
125
+ <button @click="showSuccess">Show Success</button>
126
+ <button @click="showError">Show Error</button>
127
+ <button @click="showWarning">Show Warning</button>
128
+ <button @click="showInfo">Show Info</button>
129
+
130
+ <!-- Required: Add the notification element to your template -->
131
+ <bl-notification />
132
+ </div>
133
+ </template>
134
+
135
+ <script setup lang="ts">
136
+ import { useNotification } from "@baklavue/composables";
137
+
138
+ const { success, error, warning, info } = useNotification();
139
+
140
+ const showSuccess = () => {
141
+ success({
142
+ caption: "Success!",
143
+ description: "Operation completed successfully.",
144
+ duration: 5,
145
+ });
146
+ };
147
+
148
+ const showError = () => {
149
+ error({
150
+ caption: "Error!",
151
+ description: "Something went wrong. Please try again.",
152
+ duration: 8,
153
+ });
154
+ };
155
+
156
+ const showWarning = () => {
157
+ warning({
158
+ caption: "Warning!",
159
+ description: "Please review your input before proceeding.",
160
+ duration: 6,
161
+ });
162
+ };
163
+
164
+ const showInfo = () => {
165
+ info({
166
+ caption: "Information",
167
+ description: "Here is some useful information for you.",
168
+ duration: 4,
169
+ });
170
+ };
171
+ </script>
172
+ ```
173
+
174
+ #### API Reference
175
+
176
+ The `useNotification` composable returns an object with the following methods:
177
+
178
+ ##### `success(notification: NotificationProps)`
179
+
180
+ Shows a success notification with green styling.
181
+
182
+ ##### `error(notification: NotificationProps)`
183
+
184
+ Shows an error notification with red styling.
185
+
186
+ ##### `warning(notification: NotificationProps)`
187
+
188
+ Shows a warning notification with orange styling.
189
+
190
+ ##### `info(notification: NotificationProps)`
191
+
192
+ Shows an info notification with blue styling.
193
+
194
+ #### Notification Props
195
+
196
+ All notification methods accept a `NotificationProps` object with the following properties:
197
+
198
+ ```typescript
199
+ interface NotificationProps {
200
+ caption?: string; // Notification title
201
+ description: string; // Notification message (required)
202
+ duration?: number; // Duration in seconds (optional)
203
+ permanent?: boolean; // Prevent auto-close
204
+ // ... other Baklava notification properties
205
+ }
206
+ ```
207
+
208
+ #### Important Notes
209
+
210
+ - **Required DOM Element**: You must include `<bl-notification>` in your template for notifications to work
211
+ - **Auto-icon**: All notifications automatically include appropriate icons based on their variant
212
+ - **Duration**: If no duration is specified, notifications will use Baklava's default duration
213
+ - **Error Handling**: The composable includes built-in error handling and will warn if the notification element is not found
214
+
215
+ ## 🏗️ Project Structure
216
+
217
+ ```
218
+ packages/composables/
219
+ ├── index.ts # Main export file
220
+ ├── csv.ts # CSV parsing, creating, and download composable
221
+ ├── notification.ts # Notification composable
222
+ ├── package.json # Package configuration
223
+ ├── tsconfig.json # TypeScript configuration
224
+ └── README.md # This file
225
+ ```
226
+
227
+ ## 🔧 Development
228
+
229
+ ### Prerequisites
230
+
231
+ - [Bun](https://bun.sh) (recommended) or Node.js
232
+ - TypeScript 5.9.2+
233
+
234
+ ### Setup
235
+
236
+ ```bash
237
+ # Install dependencies
238
+ bun install
239
+
240
+ # Run the package
241
+ bun run index.ts
242
+ ```
243
+
244
+ ### TypeScript Configuration
245
+
246
+ This package uses strict TypeScript configuration with:
247
+
248
+ - ESNext target and modules
249
+ - Strict type checking
250
+ - Bundler-friendly module resolution
251
+ - Unused variable/parameter detection
252
+
253
+ ## 🤝 Contributing
254
+
255
+ 1. Fork the repository
256
+ 2. Create a feature branch
257
+ 3. Make your changes
258
+ 4. Add tests if applicable
259
+ 5. Submit a pull request
260
+
261
+ ## 📄 License
262
+
263
+ This project is part of the Baklavue ecosystem. See the main project for license information.
264
+
265
+ ## 🔗 Related Packages
266
+
267
+ - `@baklavue/ui` - Baklava UI components for Vue
268
+ - `@trendyol/baklava` - Core Baklava design system
269
+
270
+ ## 📞 Support
271
+
272
+ For issues and questions:
273
+
274
+ - Check the [Baklava documentation](https://baklava.trendyol.com/)
275
+ - Open an issue in the project repository
276
+ - Review the examples in the playground directory
package/csv.ts ADDED
@@ -0,0 +1,126 @@
1
+ import Papa from "papaparse";
2
+ import type { ParseError, ParseMeta, ParseResult } from "papaparse";
3
+
4
+ /** Re-exported for consumer use */
5
+ export type { ParseError, ParseMeta };
6
+
7
+ /**
8
+ * Options for parsing CSV strings or files.
9
+ */
10
+ export interface CsvParseOptions {
11
+ /** Delimiter character. Leave empty to auto-detect. */
12
+ delimiter?: string;
13
+ /** If true, first row is treated as header (returns array of objects). */
14
+ header?: boolean;
15
+ /** If true, numeric and boolean values are converted to their types. */
16
+ dynamicTyping?: boolean;
17
+ /** If true, skip completely empty lines. Use 'greedy' to also skip whitespace-only lines. */
18
+ skipEmptyLines?: boolean | "greedy";
19
+ }
20
+
21
+ /**
22
+ * Options for creating CSV strings or downloading.
23
+ */
24
+ export interface CsvCreateOptions {
25
+ /** Delimiter character. Default: comma. */
26
+ delimiter?: string;
27
+ /** If false, omit header row. Ignored for array of arrays. Default: true. */
28
+ header?: boolean;
29
+ /** Column order for array of objects. Uses object keys if not specified. */
30
+ columns?: string[];
31
+ /** If true, escape formulae injection (values starting with =, +, -, @). */
32
+ escapeFormulae?: boolean;
33
+ }
34
+
35
+ /** Parse result with data, errors, and meta. Use ParseResult<T> for typed rows. */
36
+ export type { ParseResult };
37
+
38
+ /** Data accepted by create() and download(): array of objects, array of arrays, or explicit fields+data. */
39
+ export type CsvData =
40
+ | Record<string, unknown>[]
41
+ | unknown[][]
42
+ | { fields: string[]; data: unknown[][] };
43
+
44
+ /**
45
+ * Composable for CSV parsing, creating, and downloading.
46
+ * Uses PapaParse for RFC 4180-compliant handling of quoted fields and edge cases.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const { parse, parseFile, create, download } = useCsv();
51
+ *
52
+ * // Parse string
53
+ * const result = parse('name,age\nAlice,30\nBob,25', { header: true });
54
+ *
55
+ * // Parse file (async)
56
+ * const fileResult = await parseFile(file, { header: true });
57
+ *
58
+ * // Create CSV
59
+ * const csv = create([{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]);
60
+ *
61
+ * // Download
62
+ * download([{ name: 'Alice', age: 30 }], 'export.csv');
63
+ * ```
64
+ */
65
+ export const useCsv = () => {
66
+ const parse = (csv: string, options?: CsvParseOptions): ParseResult<unknown> => {
67
+ return Papa.parse(csv, {
68
+ delimiter: options?.delimiter ?? "",
69
+ header: options?.header ?? false,
70
+ dynamicTyping: options?.dynamicTyping ?? false,
71
+ skipEmptyLines: options?.skipEmptyLines ?? false,
72
+ });
73
+ };
74
+
75
+ const parseFile = (
76
+ file: File,
77
+ options?: CsvParseOptions
78
+ ): Promise<ParseResult<unknown>> => {
79
+ return new Promise((resolve, reject) => {
80
+ Papa.parse(file, {
81
+ delimiter: options?.delimiter ?? "",
82
+ header: options?.header ?? false,
83
+ dynamicTyping: options?.dynamicTyping ?? false,
84
+ skipEmptyLines: options?.skipEmptyLines ?? false,
85
+ complete: (results: ParseResult<unknown>) => resolve(results),
86
+ error: (err: Error) => reject(err),
87
+ });
88
+ });
89
+ };
90
+
91
+ const create = (data: CsvData, options?: CsvCreateOptions): string => {
92
+ return Papa.unparse(data as Parameters<typeof Papa.unparse>[0], {
93
+ delimiter: options?.delimiter ?? ",",
94
+ header: options?.header ?? true,
95
+ columns: options?.columns ?? undefined,
96
+ escapeFormulae: options?.escapeFormulae ?? false,
97
+ });
98
+ };
99
+
100
+ const download = (
101
+ data: CsvData,
102
+ filename = "export.csv",
103
+ options?: CsvCreateOptions
104
+ ): void => {
105
+ if (typeof document === "undefined") return;
106
+
107
+ const csv = create(data, options);
108
+ const BOM = "\uFEFF";
109
+ const blob = new Blob([BOM + csv], { type: "text/csv;charset=utf-8" });
110
+ const url = URL.createObjectURL(blob);
111
+
112
+ const link = document.createElement("a");
113
+ link.href = url;
114
+ link.download = filename.endsWith(".csv") ? filename : `${filename}.csv`;
115
+ link.click();
116
+
117
+ URL.revokeObjectURL(url);
118
+ };
119
+
120
+ return {
121
+ parse,
122
+ parseFile,
123
+ create,
124
+ download,
125
+ };
126
+ };
package/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ export {
2
+ useCsv,
3
+ type CsvCreateOptions,
4
+ type CsvData,
5
+ type CsvParseOptions,
6
+ type ParseError,
7
+ type ParseMeta,
8
+ type ParseResult,
9
+ } from "./csv";
10
+ export { useNotification } from "./notification";
11
+ export {
12
+ useScrollToError,
13
+ type ScrollToErrorOptions,
14
+ type ScrollToErrorTarget,
15
+ } from "./scrollToError";
16
+ export {
17
+ useBaklavaTheme,
18
+ type ApplyThemeOptions,
19
+ type BaklavaThemeBorderRadius,
20
+ type BaklavaThemeColors,
21
+ type BaklavaThemePreset,
22
+ type BaklavaThemePresetRecord,
23
+ type BaklavaThemeSize,
24
+ type BaklavaThemeTypography,
25
+ type BaklavaThemeZIndex,
26
+ } from "./theme";
@@ -0,0 +1,69 @@
1
+ import type { NotificationProps } from "@trendyol/baklava/dist/components/notification/bl-notification";
2
+ import type { NotificationVariant } from "@trendyol/baklava/dist/components/notification/card/bl-notification-card";
3
+
4
+ interface NotificationElement extends HTMLElement {
5
+ addNotification: (options: NotificationProps) => void;
6
+ }
7
+
8
+ interface NotificationOptions extends Omit<NotificationProps, "variant"> {
9
+ variant: NotificationVariant;
10
+ }
11
+
12
+ export const useNotification = () => {
13
+ const getNotificationElement = (): NotificationElement | null => {
14
+ return document.querySelector(
15
+ "bl-notification",
16
+ ) as NotificationElement | null;
17
+ };
18
+
19
+ const createNotification = (options: NotificationOptions): void => {
20
+ const notificationElement = getNotificationElement();
21
+
22
+ if (!notificationElement) {
23
+ console.warn(
24
+ "Notification element not found. Make sure <bl-notification> is present in the DOM.",
25
+ );
26
+ return;
27
+ }
28
+
29
+ notificationElement.addNotification({
30
+ ...options,
31
+ icon: true,
32
+ });
33
+ };
34
+
35
+ const success = (notification: Omit<NotificationProps, "variant">): void => {
36
+ createNotification({
37
+ ...notification,
38
+ variant: "success",
39
+ });
40
+ };
41
+
42
+ const error = (notification: Omit<NotificationProps, "variant">): void => {
43
+ createNotification({
44
+ ...notification,
45
+ variant: "error",
46
+ });
47
+ };
48
+
49
+ const warning = (notification: Omit<NotificationProps, "variant">): void => {
50
+ createNotification({
51
+ ...notification,
52
+ variant: "warning",
53
+ });
54
+ };
55
+
56
+ const info = (notification: Omit<NotificationProps, "variant">): void => {
57
+ createNotification({
58
+ ...notification,
59
+ variant: "info",
60
+ });
61
+ };
62
+
63
+ return {
64
+ success,
65
+ error,
66
+ warning,
67
+ info,
68
+ };
69
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@baklavue/composables",
3
+ "version": "1.0.0-preview.1",
4
+ "description": "Vue 3 composables for Trendyol Baklava Design System",
5
+ "author": "erbilnas",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/erbilnas/baklavue/tree/main/packages/composables#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/erbilnas/baklavue.git",
11
+ "directory": "packages/composables"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/erbilnas/baklavue/issues"
15
+ },
16
+ "keywords": [
17
+ "vue",
18
+ "vue3",
19
+ "baklava",
20
+ "trendyol",
21
+ "design-system",
22
+ "composables"
23
+ ],
24
+ "module": "index.ts",
25
+ "type": "module",
26
+ "scripts": {
27
+ "release": "semantic-release -e semantic-release-monorepo",
28
+ "release:dry-run": "semantic-release --dry-run -e semantic-release-monorepo"
29
+ },
30
+ "devDependencies": {
31
+ "@trendyol/baklava": "^3.4.2",
32
+ "@types/bun": "latest",
33
+ "@types/papaparse": "^5.5.2"
34
+ },
35
+ "peerDependencies": {
36
+ "typescript": "^5.9.2",
37
+ "vue": ">=3.0.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "papaparse": "^5.5.3"
44
+ }
45
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Focusable element selectors for finding form controls.
3
+ * Includes native inputs and Baklava custom components.
4
+ */
5
+ const FOCUSABLE_SELECTORS =
6
+ "input, select, textarea, button, bl-select, bl-input";
7
+
8
+ /**
9
+ * Options for customizing scroll-to-error behavior.
10
+ */
11
+ export interface ScrollToErrorOptions {
12
+ /** Scroll behavior. Default: `'smooth'` */
13
+ scrollBehavior?: ScrollBehavior;
14
+ /** Vertical scroll position. Default: `'center'` */
15
+ block?: ScrollLogicalPosition;
16
+ /** CSS class to apply for highlight effect. Default: `'error-shine'` */
17
+ shineClass?: string;
18
+ /** Duration in ms to keep the shine class. Use `0` to disable. Default: `2500` */
19
+ shineDuration?: number;
20
+ /** Whether to attempt focusing the first focusable element. Default: `true` */
21
+ focus?: boolean;
22
+ /** Delay in ms before focus attempt. Default: `300` */
23
+ focusDelay?: number;
24
+ }
25
+
26
+ /** Target that can be resolved to an HTMLElement: selector, element, or object with scrollTarget. */
27
+ export type ScrollToErrorTarget =
28
+ | string
29
+ | HTMLElement
30
+ | { scrollTarget: string };
31
+
32
+ /**
33
+ * Resolves the target to an HTMLElement.
34
+ *
35
+ * @param target - CSS selector, HTMLElement, or object with scrollTarget
36
+ * @returns The resolved element or null if not found
37
+ */
38
+ function resolveTarget(target: ScrollToErrorTarget): HTMLElement | null {
39
+ if (typeof document === "undefined") return null;
40
+
41
+ if (typeof target === "string") {
42
+ const el = document.querySelector(target);
43
+ return el instanceof HTMLElement ? el : null;
44
+ }
45
+
46
+ if (target instanceof HTMLElement) {
47
+ return target;
48
+ }
49
+
50
+ if (target && typeof target === "object" && "scrollTarget" in target) {
51
+ const selector = target.scrollTarget;
52
+ if (!selector || typeof selector !== "string") return null;
53
+ const el = document.querySelector(selector);
54
+ return el instanceof HTMLElement ? el : null;
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * Finds the first focusable element within the given element.
62
+ * Prefers inner input/select/textarea for custom components like bl-select.
63
+ *
64
+ * @param element - Container element to search within
65
+ * @returns The focusable element or null
66
+ */
67
+ function findFocusable(element: HTMLElement): HTMLElement | null {
68
+ const focusable = element.querySelector(FOCUSABLE_SELECTORS);
69
+ if (!(focusable instanceof HTMLElement)) return null;
70
+
71
+ // Prefer inner input/select/textarea for custom components
72
+ const innerInput = focusable.querySelector("input, select, textarea");
73
+ if (innerInput instanceof HTMLElement) {
74
+ return innerInput;
75
+ }
76
+ return focusable;
77
+ }
78
+
79
+ /**
80
+ * Composable for scrolling to an element with validation error.
81
+ * Scrolls into view, optionally applies a highlight effect, and focuses the first focusable control.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const { scrollToError } = useScrollToError();
86
+ *
87
+ * // From validation error object
88
+ * scrollToError(validationError);
89
+ *
90
+ * // Direct selector
91
+ * scrollToError('[data-field="tags"]');
92
+ *
93
+ * // With custom options
94
+ * scrollToError('[data-field="tags"]', { shineClass: 'my-shine', shineDuration: 1500 });
95
+ * ```
96
+ */
97
+ export const useScrollToError = () => {
98
+ /**
99
+ * Scrolls to the target element and optionally applies highlight and focus.
100
+ *
101
+ * @param target - CSS selector, HTMLElement, or object with scrollTarget (e.g. validation error)
102
+ * @param options - Optional overrides for scroll, shine, and focus behavior
103
+ */
104
+ const scrollToError = (
105
+ target: ScrollToErrorTarget,
106
+ options?: ScrollToErrorOptions,
107
+ ): void => {
108
+ if (typeof document === "undefined") return;
109
+
110
+ const element = resolveTarget(target);
111
+ if (!element) return;
112
+
113
+ const {
114
+ scrollBehavior = "smooth",
115
+ block = "center",
116
+ shineClass = "error-shine",
117
+ shineDuration = 2500,
118
+ focus = true,
119
+ focusDelay = 300,
120
+ } = options ?? {};
121
+
122
+ element.scrollIntoView({ behavior: scrollBehavior, block });
123
+
124
+ // Apply shine effect
125
+ let targetElement: HTMLElement | null = null;
126
+ const formControl =
127
+ element.closest(".bl-form-control, [class*='bl-']") || element;
128
+ targetElement = formControl instanceof HTMLElement ? formControl : element;
129
+
130
+ if (targetElement && shineClass && shineDuration > 0) {
131
+ targetElement.classList.add(shineClass);
132
+ setTimeout(() => {
133
+ targetElement?.classList.remove(shineClass);
134
+ }, shineDuration);
135
+ }
136
+
137
+ // Focus the first focusable element
138
+ if (focus && element instanceof HTMLElement) {
139
+ const focusableElement = findFocusable(element);
140
+ if (focusableElement) {
141
+ setTimeout(() => {
142
+ focusableElement.focus();
143
+ }, focusDelay);
144
+ }
145
+ }
146
+ };
147
+
148
+ return {
149
+ scrollToError,
150
+ };
151
+ };
package/theme.ts ADDED
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Built-in presets. Use 'vue' to apply Vue.js brand colors.
3
+ */
4
+ export type BaklavaThemePreset = "vue" | "default";
5
+
6
+ /**
7
+ * Custom preset: map of CSS variable names to values.
8
+ * Use this to pass your own preset or compose with built-in presets.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const myPreset: BaklavaThemePresetRecord = {
13
+ * '--bl-color-primary': '#ea4c89',
14
+ * '--bl-color-primary-highlight': '#d6427a',
15
+ * };
16
+ * applyTheme({ preset: myPreset });
17
+ * ```
18
+ */
19
+ export type BaklavaThemePresetRecord = Record<string, string>;
20
+
21
+ /**
22
+ * Custom color overrides for Baklava design tokens.
23
+ * Maps to --bl-color-* CSS variables.
24
+ */
25
+ export interface BaklavaThemeColors {
26
+ primary?: string;
27
+ primaryHighlight?: string;
28
+ primaryContrast?: string;
29
+ success?: string;
30
+ successHighlight?: string;
31
+ successContrast?: string;
32
+ danger?: string;
33
+ dangerHighlight?: string;
34
+ dangerContrast?: string;
35
+ warning?: string;
36
+ warningHighlight?: string;
37
+ warningContrast?: string;
38
+ info?: string;
39
+ infoHighlight?: string;
40
+ infoContrast?: string;
41
+ neutralDarkest?: string;
42
+ neutralDarker?: string;
43
+ neutralDark?: string;
44
+ neutralLight?: string;
45
+ neutralLighter?: string;
46
+ neutralLightest?: string;
47
+ neutralFull?: string;
48
+ }
49
+
50
+ /**
51
+ * Border radius overrides for Baklava design tokens.
52
+ * Maps to --bl-border-radius-* CSS variables.
53
+ */
54
+ export interface BaklavaThemeBorderRadius {
55
+ xs?: string;
56
+ s?: string;
57
+ m?: string;
58
+ l?: string;
59
+ pill?: string;
60
+ circle?: string;
61
+ }
62
+
63
+ /**
64
+ * Size and spacing overrides for Baklava design tokens.
65
+ * Maps to --bl-size-* CSS variables.
66
+ */
67
+ export interface BaklavaThemeSize {
68
+ "4xs"?: string;
69
+ "3xs"?: string;
70
+ "2xs"?: string;
71
+ xs?: string;
72
+ s?: string;
73
+ m?: string;
74
+ l?: string;
75
+ xl?: string;
76
+ "2xl"?: string;
77
+ "3xl"?: string;
78
+ "4xl"?: string;
79
+ "5xl"?: string;
80
+ "6xl"?: string;
81
+ }
82
+
83
+ /**
84
+ * Typography overrides for Baklava design tokens.
85
+ * Maps to --bl-font-* CSS variables.
86
+ */
87
+ export interface BaklavaThemeTypography {
88
+ fontFamily?: string;
89
+ fontSize?: Record<string, string>;
90
+ fontWeight?: Record<string, number>;
91
+ }
92
+
93
+ /**
94
+ * Z-index overrides for Baklava design tokens.
95
+ * Maps to --bl-index-* CSS variables.
96
+ */
97
+ export interface BaklavaThemeZIndex {
98
+ deep?: number;
99
+ base?: number;
100
+ popover?: number;
101
+ tooltip?: number;
102
+ sticky?: number;
103
+ overlay?: number;
104
+ dialog?: number;
105
+ notification?: number;
106
+ }
107
+
108
+ export interface ApplyThemeOptions {
109
+ /** Built-in preset name ('vue', 'default') or your own preset object */
110
+ preset?: BaklavaThemePreset | BaklavaThemePresetRecord;
111
+ colors?: Partial<BaklavaThemeColors>;
112
+ borderRadius?: Partial<BaklavaThemeBorderRadius>;
113
+ size?: Partial<BaklavaThemeSize>;
114
+ typography?: Partial<BaklavaThemeTypography>;
115
+ zIndex?: Partial<BaklavaThemeZIndex>;
116
+ }
117
+
118
+ const VUE_PRESET: Record<string, string> = {
119
+ "--bl-color-primary": "#41b883",
120
+ "--bl-color-primary-highlight": "#3aa876",
121
+ "--bl-color-primary-contrast": "#e7f9ef",
122
+ "--bl-color-success": "#41b883",
123
+ "--bl-color-success-highlight": "#3aa876",
124
+ "--bl-color-success-contrast": "#e7f9ef",
125
+ "--bl-color-neutral-darker": "#34495e",
126
+ "--bl-color-neutral-darkest": "#2c3e50",
127
+ };
128
+
129
+ const BORDER_RADIUS_MAP: Record<keyof BaklavaThemeBorderRadius, string> = {
130
+ xs: "--bl-border-radius-xs",
131
+ s: "--bl-border-radius-s",
132
+ m: "--bl-border-radius-m",
133
+ l: "--bl-border-radius-l",
134
+ pill: "--bl-border-radius-pill",
135
+ circle: "--bl-border-radius-circle",
136
+ };
137
+
138
+ const SIZE_MAP: Record<keyof BaklavaThemeSize, string> = {
139
+ "4xs": "--bl-size-4xs",
140
+ "3xs": "--bl-size-3xs",
141
+ "2xs": "--bl-size-2xs",
142
+ xs: "--bl-size-xs",
143
+ s: "--bl-size-s",
144
+ m: "--bl-size-m",
145
+ l: "--bl-size-l",
146
+ xl: "--bl-size-xl",
147
+ "2xl": "--bl-size-2xl",
148
+ "3xl": "--bl-size-3xl",
149
+ "4xl": "--bl-size-4xl",
150
+ "5xl": "--bl-size-5xl",
151
+ "6xl": "--bl-size-6xl",
152
+ };
153
+
154
+ const Z_INDEX_MAP: Record<keyof BaklavaThemeZIndex, string> = {
155
+ deep: "--bl-index-deep",
156
+ base: "--bl-index-base",
157
+ popover: "--bl-index-popover",
158
+ tooltip: "--bl-index-tooltip",
159
+ sticky: "--bl-index-sticky",
160
+ overlay: "--bl-index-overlay",
161
+ dialog: "--bl-index-dialog",
162
+ notification: "--bl-index-notification",
163
+ };
164
+
165
+ function buildCssVariables(options: ApplyThemeOptions = {}): string {
166
+ const vars: Record<string, string> = {};
167
+ const { preset, colors, borderRadius, size, typography, zIndex } = options;
168
+
169
+ if (preset) {
170
+ if (typeof preset === "string") {
171
+ if (preset === "vue") Object.assign(vars, VUE_PRESET);
172
+ } else {
173
+ Object.assign(vars, preset);
174
+ }
175
+ }
176
+
177
+ if (colors) {
178
+ const colorMap: Record<keyof BaklavaThemeColors, string> = {
179
+ primary: "--bl-color-primary",
180
+ primaryHighlight: "--bl-color-primary-highlight",
181
+ primaryContrast: "--bl-color-primary-contrast",
182
+ success: "--bl-color-success",
183
+ successHighlight: "--bl-color-success-highlight",
184
+ successContrast: "--bl-color-success-contrast",
185
+ danger: "--bl-color-danger",
186
+ dangerHighlight: "--bl-color-danger-highlight",
187
+ dangerContrast: "--bl-color-danger-contrast",
188
+ warning: "--bl-color-warning",
189
+ warningHighlight: "--bl-color-warning-highlight",
190
+ warningContrast: "--bl-color-warning-contrast",
191
+ info: "--bl-color-info",
192
+ infoHighlight: "--bl-color-info-highlight",
193
+ infoContrast: "--bl-color-info-contrast",
194
+ neutralDarkest: "--bl-color-neutral-darkest",
195
+ neutralDarker: "--bl-color-neutral-darker",
196
+ neutralDark: "--bl-color-neutral-dark",
197
+ neutralLight: "--bl-color-neutral-light",
198
+ neutralLighter: "--bl-color-neutral-lighter",
199
+ neutralLightest: "--bl-color-neutral-lightest",
200
+ neutralFull: "--bl-color-neutral-full",
201
+ };
202
+
203
+ for (const [key, value] of Object.entries(colors)) {
204
+ if (value && key in colorMap) {
205
+ vars[colorMap[key as keyof BaklavaThemeColors]] = value;
206
+ }
207
+ }
208
+ }
209
+
210
+ if (borderRadius) {
211
+ for (const [key, value] of Object.entries(borderRadius)) {
212
+ if (value && key in BORDER_RADIUS_MAP) {
213
+ vars[BORDER_RADIUS_MAP[key as keyof BaklavaThemeBorderRadius]] = value;
214
+ }
215
+ }
216
+ }
217
+
218
+ if (size) {
219
+ for (const [key, value] of Object.entries(size)) {
220
+ if (value && key in SIZE_MAP) {
221
+ vars[SIZE_MAP[key as keyof BaklavaThemeSize]] = value;
222
+ }
223
+ }
224
+ }
225
+
226
+ if (typography) {
227
+ if (typography.fontFamily) vars["--bl-font-family"] = typography.fontFamily;
228
+ if (typography.fontSize) {
229
+ for (const [key, value] of Object.entries(typography.fontSize)) {
230
+ if (value) vars[`--bl-font-size-${key}`] = value;
231
+ }
232
+ }
233
+ if (typography.fontWeight) {
234
+ for (const [key, value] of Object.entries(typography.fontWeight)) {
235
+ if (value != null) vars[`--bl-font-weight-${key}`] = String(value);
236
+ }
237
+ }
238
+ }
239
+
240
+ if (zIndex) {
241
+ for (const [key, value] of Object.entries(zIndex)) {
242
+ if (value != null && key in Z_INDEX_MAP) {
243
+ vars[Z_INDEX_MAP[key as keyof BaklavaThemeZIndex]] = String(value);
244
+ }
245
+ }
246
+ }
247
+
248
+ const rules = Object.entries(vars)
249
+ .map(([prop, val]) => ` ${prop}: ${val};`)
250
+ .join("\n");
251
+
252
+ return rules ? `:root {\n${rules}\n}` : "";
253
+ }
254
+
255
+ const STYLE_ID = "baklavue-theme-overrides";
256
+
257
+ /**
258
+ * Composable to overwrite Baklava design system colors.
259
+ * Use the 'vue' preset, pass your own preset object, or override specific colors.
260
+ *
261
+ * @example
262
+ * ```ts
263
+ * import { useBaklavaTheme } from '@baklavue/composables'
264
+ *
265
+ * // Vue preset
266
+ * useBaklavaTheme().applyTheme({ preset: 'vue' })
267
+ *
268
+ * // Custom preset
269
+ * useBaklavaTheme().applyTheme({
270
+ * preset: {
271
+ * '--bl-color-primary': '#ea4c89',
272
+ * '--bl-color-primary-highlight': '#d6427a',
273
+ * }
274
+ * })
275
+ *
276
+ * // Custom colors
277
+ * useBaklavaTheme().applyTheme({
278
+ * colors: { primary: '#41B883', primaryHighlight: '#3aa876' }
279
+ * })
280
+ * ```
281
+ */
282
+ export const useBaklavaTheme = () => {
283
+ const applyTheme = (options: ApplyThemeOptions = {}): void => {
284
+ if (typeof document === "undefined") return;
285
+
286
+ const css = buildCssVariables(options);
287
+
288
+ if (!css) return;
289
+
290
+ let styleEl = document.getElementById(STYLE_ID) as HTMLStyleElement | null;
291
+
292
+ if (!styleEl) {
293
+ styleEl = document.createElement("style");
294
+ styleEl.id = STYLE_ID;
295
+ document.head.appendChild(styleEl);
296
+ }
297
+
298
+ styleEl.textContent = css;
299
+ // Move to end of head so we override any lazy-loaded styles (e.g. from VitePress route chunks)
300
+ document.head.appendChild(styleEl);
301
+ };
302
+
303
+ return {
304
+ applyTheme,
305
+ };
306
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Enable latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+
22
+ // Some stricter flags (disabled by default)
23
+ "noUnusedLocals": true,
24
+ "noUnusedParameters": true,
25
+ "noPropertyAccessFromIndexSignature": true,
26
+ "noImplicitAny": true
27
+ }
28
+ }