@g1cloud/bluesea 5.0.0-beta.26 → 5.0.0-beta.28
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 +21 -0
- package/bin/install-claude-skill.mjs +74 -0
- package/css/bluesea.css +61 -7
- package/dist/{BSAlertModal-CCdaoT-g.js → BSAlertModal-BpbJuAe1.js} +1 -1
- package/dist/{BSGridColumnSettingModal-CMJqpWzY.js → BSGridColumnSettingModal-8MqhRWkU.js} +1 -1
- package/dist/{BSRichTextMaximizedModal-Byrr_L8I.js → BSRichTextMaximizedModal-C86Skc5v.js} +1 -1
- package/dist/{BSYesNoModal-BljzNd5_.js → BSYesNoModal-CHbktVAj.js} +1 -1
- package/dist/{BSYoutubeInputModal-BZR0jJvt.js → BSYoutubeInputModal-JKnr4hGE.js} +1 -1
- package/dist/{ImageInsertModal-wdRGMEHH.js → ImageInsertModal-DQwkQJ8b.js} +2 -2
- package/dist/{ImageProperties.vue_vue_type_script_setup_true_lang-DNqql2HK.js → ImageProperties.vue_vue_type_script_setup_true_lang-BsMcsXdh.js} +1 -1
- package/dist/{ImagePropertiesModal-BumfiYFu.js → ImagePropertiesModal-X7blKqTy.js} +2 -2
- package/dist/{LinkPropertiesModal-C-cq00aG.js → LinkPropertiesModal-DGiiTivW.js} +1 -1
- package/dist/{TableInsertModal-DWy7cSQz.js → TableInsertModal-CupFfnOG.js} +1 -1
- package/dist/{TablePropertiesModal-BDir2XM5.js → TablePropertiesModal-CfK9i7Q5.js} +1 -1
- package/dist/{VideoInsertModal-s4eT3Ofx.js → VideoInsertModal-BwRRgibx.js} +2 -2
- package/dist/{VideoProperties.vue_vue_type_script_setup_true_lang-B2SQASHh.js → VideoProperties.vue_vue_type_script_setup_true_lang-zEMpmzTZ.js} +1 -1
- package/dist/{VideoPropertiesModal-zHc0vcQs.js → VideoPropertiesModal-Dn6AzhPy.js} +2 -2
- package/dist/{YoutubeInsertModal-CdcIzmFl.js → YoutubeInsertModal-DCn5bhN5.js} +2 -2
- package/dist/{YoutubeProperties.vue_vue_type_script_setup_true_lang-CWqGTFe5.js → YoutubeProperties.vue_vue_type_script_setup_true_lang-B-YVlp4Y.js} +1 -1
- package/dist/{YoutubePropertiesModal-CAd6dZ6E.js → YoutubePropertiesModal-Dg-n8cTv.js} +2 -2
- package/dist/bluesea.css +53 -7
- package/dist/bluesea.js +1 -1
- package/dist/bluesea.umd.cjs +623 -454
- package/dist/component/input/BSImageUpload.vue.d.ts +4 -0
- package/dist/component/input/BSMediaPreview.vue.d.ts +2 -0
- package/dist/component/input/BSMediaPreviewOverlay.vue.d.ts +44 -0
- package/dist/component/input/BSMultiImageUpload.vue.d.ts +2 -0
- package/dist/component/input/BSPositionedImageUpload.vue.d.ts +2 -0
- package/dist/{index-pO-xtezx.js → index-e3O4IL4V.js} +557 -388
- package/dist/text/i18n.d.ts +2 -1
- package/package.json +6 -1
- package/skills/bluesea-ui/SKILL.md +312 -0
- package/skills/bluesea-ui/references/components.md +189 -0
- package/skills/bluesea-ui/references/grid.md +159 -0
- package/skills/bluesea-ui/references/i18n.md +126 -0
- package/skills/bluesea-ui/references/validation.md +176 -0
- package/text/bluesea_text_en.json +248 -964
- package/text/bluesea_text_fr.json +248 -964
- package/text/bluesea_text_ja.json +248 -964
- package/text/bluesea_text_ko.json +248 -964
- package/text/bluesea_text_zh.json +248 -964
package/dist/text/i18n.d.ts
CHANGED
|
@@ -6,9 +6,10 @@ export type TextRecord = {
|
|
|
6
6
|
export type ResolvedTextRecord = TextRecord & {
|
|
7
7
|
resolved: boolean;
|
|
8
8
|
};
|
|
9
|
+
export type CompactTexts = Record<string, string | (string | number)[]>;
|
|
9
10
|
export declare class I18NTexts {
|
|
10
11
|
private localeTexts;
|
|
11
|
-
addTexts(locale: string,
|
|
12
|
+
addTexts(locale: string, input: TextRecord[] | CompactTexts): void;
|
|
12
13
|
/**
|
|
13
14
|
* 현재 언어코드에서 `key` 에 해당하는 `TextRecord` 를 리턴한다.
|
|
14
15
|
* @param key
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@g1cloud/bluesea",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "5.0.0-beta.
|
|
4
|
+
"version": "5.0.0-beta.28",
|
|
5
5
|
"description": "Ui Library for g1cloud BackOffice.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -14,11 +14,16 @@
|
|
|
14
14
|
"css",
|
|
15
15
|
"scss",
|
|
16
16
|
"text",
|
|
17
|
+
"skills",
|
|
18
|
+
"bin",
|
|
17
19
|
"!dist/tests"
|
|
18
20
|
],
|
|
19
21
|
"module": "dist/bluesea.js",
|
|
20
22
|
"main": "dist/bluesea.js",
|
|
21
23
|
"types": "dist/index.d.ts",
|
|
24
|
+
"bin": {
|
|
25
|
+
"bluesea-install-skill": "./bin/install-claude-skill.mjs"
|
|
26
|
+
},
|
|
22
27
|
"dependencies": {
|
|
23
28
|
"@codemirror/lang-css": "^6.2.1",
|
|
24
29
|
"@codemirror/lang-html": "^6.4.9",
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bluesea-ui
|
|
3
|
+
description: How to build g1cloud BackOffice UIs with the @g1cloud/bluesea Vue 3 component library — covers app bootstrap (configureBluesea, plugins), BS* components (BSButton, BSTextInput, BSGrid, BSSelect, BSModal, etc.), the FieldValidator/FormValidator pattern, useModal / showNotification, MultiLangText + i18n, SavePoint for inline edits, and the DefaultFrame shell. Use this skill whenever the user works in a project that imports from "@g1cloud/bluesea" or mentions any BS* component, BSGrid, BSModal, configureBluesea, useModal, MultiLangText, FieldValidator, formValidator, ValidationFailedError, SavePoint, or DefaultFrame — even when they only show a Vue snippet without naming the library. Also apply when the user asks about Bluesea, 블루씨, g1cloud BackOffice UI, or pastes code using <BSButton>, <BSTextInput>, <BSGrid>, etc.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @g1cloud/bluesea — Vue 3 UI library for g1cloud BackOffice
|
|
7
|
+
|
|
8
|
+
Bluesea is an enterprise-focused Vue 3 component library. Its design goals shape every API in this skill, so read them first — the patterns that look unusual usually follow from here:
|
|
9
|
+
|
|
10
|
+
- **Strict types.** Every component has explicit prop types. Prefer `<script setup lang="ts">` with `defineProps<{...}>()`. Don't invent loose props.
|
|
11
|
+
- **Unidirectional data flow.** Props down, `update:xxx` events up. Two-way binding is always `v-model:xxx`.
|
|
12
|
+
- **i18n-first.** Almost every text prop (caption, placeholder, tooltip, error message) accepts `MultiLangText`, not just `string`. See "MultiLangText" below.
|
|
13
|
+
- **Inversion of control.** Components expose scoped slots + plugin/extension hooks. Don't fork the component — fill the slot or pass an extension.
|
|
14
|
+
- **Validation is a first-class concern.** Inputs register themselves with a `FormValidator`. Submit handlers throw `ValidationFailedError`. See "Validation".
|
|
15
|
+
|
|
16
|
+
When you are unsure about a component's props, read the `.vue` file directly at `packages/bluesea/src/component/<group>/<Name>.vue` — the `defineProps<{...}>()` block is the source of truth. This is faster and more reliable than guessing.
|
|
17
|
+
|
|
18
|
+
## 1. Project bootstrap
|
|
19
|
+
|
|
20
|
+
Every Bluesea app calls `configureBluesea(...)` before `createApp(...).mount(...)`, and installs the modal / notification / context-menu plugins that the library depends on. A realistic `main.ts` looks like this (trim to what you need):
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createApp } from 'vue'
|
|
24
|
+
import '@g1cloud/bluesea/css/bluesea.css'
|
|
25
|
+
import '@g1cloud/bluesea/css/frame-default.css' // only if you use DefaultFrame
|
|
26
|
+
import {
|
|
27
|
+
configureBluesea,
|
|
28
|
+
createModalPlugin,
|
|
29
|
+
createContextMenuPlugin,
|
|
30
|
+
LocalStorageGridPreferenceStore,
|
|
31
|
+
vT,
|
|
32
|
+
} from '@g1cloud/bluesea'
|
|
33
|
+
import App from './App.vue'
|
|
34
|
+
|
|
35
|
+
configureBluesea({
|
|
36
|
+
locales: ['ko', 'en', 'ja'],
|
|
37
|
+
defaultLocale: 'ko',
|
|
38
|
+
currentLocale: 'ko',
|
|
39
|
+
dataLocales: ['ko', 'en', 'ja'], // used by BSMultiLang* inputs
|
|
40
|
+
currentDataLocale: 'ko',
|
|
41
|
+
timeZone: 'Asia/Seoul',
|
|
42
|
+
dateFormat: 'YYYY-MM-DD HH:mm',
|
|
43
|
+
defaultCurrencyCode: 'KRW',
|
|
44
|
+
gridPreferenceStore: new LocalStorageGridPreferenceStore(), // persists BSGrid column widths/order
|
|
45
|
+
fileUrlResolver: (url) => url?.startsWith('http') ? url : `https://cdn.example.com/${url}`,
|
|
46
|
+
componentConfig: {
|
|
47
|
+
telInput: { requiredCountryNo: true, countries: [{ code: 'KR', name: 'Korea (+82)' }] },
|
|
48
|
+
addressInput:{ countries: [{ code: 'KR', name: 'Korea' }] },
|
|
49
|
+
calendar: { startYear: '-40', endYear: '+40' }, // relative to this year
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
createApp(App)
|
|
54
|
+
.use(createModalPlugin())
|
|
55
|
+
.use(createContextMenuPlugin())
|
|
56
|
+
// BSNotificationContainer does not need a plugin — just mount it once (see §4)
|
|
57
|
+
.directive('t', vT) // enables v-t="multiLangText"
|
|
58
|
+
.mount('#app')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Global state lives on the reactive `blueseaConfig` object; call `blueseaConfig.setCurrentLocale('en')` to switch language at runtime and every `v-t` / caption updates.
|
|
62
|
+
|
|
63
|
+
You must also mount the container components somewhere near the app root — they are the portals the plugins render into:
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<template>
|
|
67
|
+
<router-view />
|
|
68
|
+
<BSModalContainer />
|
|
69
|
+
<BSNotificationContainer />
|
|
70
|
+
<BSContextMenuContainer />
|
|
71
|
+
</template>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 2. MultiLangText (almost every text prop)
|
|
75
|
+
|
|
76
|
+
`MultiLangText` is the union that lets captions be either a plain string, a per-locale record, or an i18n key with args:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
type MultiLangText =
|
|
80
|
+
| string // plain — 'Save'
|
|
81
|
+
| Record<LocaleName, string> // per-locale — { ko: '저장', en: 'Save' }
|
|
82
|
+
| { key: string; args?: unknown[] } // i18n key — { key: 'btn.save' }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use i18n keys by default in real apps so the UI follows the user's locale. Register texts with `i18n.addTexts(locale, [...])` and render them with the `v-t` directive or the `t()` helper:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { i18n, t, interpretMultiLangText } from '@g1cloud/bluesea'
|
|
89
|
+
|
|
90
|
+
i18n.addTexts('ko', [{ key: 'btn.save', text: '저장' }])
|
|
91
|
+
i18n.addTexts('en', [{ key: 'btn.save', text: 'Save' }])
|
|
92
|
+
|
|
93
|
+
// inside <script setup>
|
|
94
|
+
const label = t({ key: 'btn.save' }) // resolves to 'Save' or '저장'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<span v-t="{ key: 'label.name' }" />
|
|
99
|
+
<BSButton :caption="{ key: 'btn.save' }" />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
When the user gives you a Korean/English string literal in demo code, pass it through as-is — components accept a plain string too. For library code that will be reused across locales, prefer keys.
|
|
103
|
+
|
|
104
|
+
## 3. Components — what exists and when to reach for it
|
|
105
|
+
|
|
106
|
+
All components come from the single package entry: `import { BSxxx } from '@g1cloud/bluesea'`. There is **no** subpath import. Types and helpers are exported from the same entry.
|
|
107
|
+
|
|
108
|
+
| Need | Component |
|
|
109
|
+
|---|---|
|
|
110
|
+
| Button / link / router-link | `BSButton` (buttonColor: default/blue/red/orange/green/gray; linkUrl, routePath) |
|
|
111
|
+
| Plain text input | `BSTextInput` (validation: required, minLength, maxLength, regExp, extraValidationRules) |
|
|
112
|
+
| Number / price / percent | `BSNumberInput`, `BSPriceInput`, `BSPercentInput` |
|
|
113
|
+
| Date / date range | `BSDateInput`, `BSDateRange`, `BSCalendar`, `BSCalendarRange`, `BSDateRangePresets` |
|
|
114
|
+
| Select | `BSSelect`, `BSMultiSelect`, `BSTreeSelect`, `BSTreeMultiSelect`, `BSPopupSelect`, `BSYesNoSelect` |
|
|
115
|
+
| Checkbox / radio | `BSCheckbox`, `BSCheckboxGroup`, `BSRadioButton`, `BSRadioButtonGroup`, `BSYesNoGroup` |
|
|
116
|
+
| Textarea / rich text / HTML editor / code editor | `BSTextArea`, `BSRichText`, `BSHtmlEditor`, `BSCodeEditor` |
|
|
117
|
+
| File / image upload | `BSFileUpload`, `BSImageUpload`, `BSMultiImageUpload`, `BSPositionedImageUpload` |
|
|
118
|
+
| Data grid | `BSGrid` + `BSGridControl` + `BSGridLookup` (see §6) |
|
|
119
|
+
| Layout | `BSCardLayout`, `BSListLayout`, `BSTabSheet`, `BSHorizontalLayoutResizer`, `BSVerticalLayoutResizer` |
|
|
120
|
+
| Tree | `BSTree`, `BSTreeControl` |
|
|
121
|
+
| Multi-language inputs | `BSMultiLangTextInput`, `BSMultiLangTextArea`, `BSMultiLangRichText`, `BSMultiLangHtmlEditor`, `BSMultiLangImageUpload` |
|
|
122
|
+
| Global entities | `BSNameInput`, `BSTelInput`, `BSAddressInput`, `BSLocaleSelect` |
|
|
123
|
+
| Popup / tooltip | `BSPopup`, `BSPopupButton`, plus `v-tooltip` directive |
|
|
124
|
+
| Frame shell | `DefaultHeader`, `DefaultBody` (BackOffice app chrome) |
|
|
125
|
+
|
|
126
|
+
Prop naming is consistent across inputs: `v-model` for the value, `name` (required for validation), `disabled`, `required`, `width` ('200px' by default), `placeholder`, `viewMode` (read-only display), `hideErrorMessage`. Date inputs add `dateFormat`, `timeZone`. Upload inputs take `StoredFile` / `StoredFile[]`.
|
|
127
|
+
|
|
128
|
+
Typical usage:
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<BSFormLabel label="이름" required />
|
|
132
|
+
<BSTextInput v-model="form.name" name="name" required :max-length="50" />
|
|
133
|
+
|
|
134
|
+
<BSFormLabel label="등급" />
|
|
135
|
+
<BSSelect v-model="form.grade" name="grade"
|
|
136
|
+
:items="grades" :label-provider="g => g.label" :key-provider="g => g.id" />
|
|
137
|
+
|
|
138
|
+
<BSButton :caption="{ key: 'btn.save' }" button-color="blue" @click="handleSave" />
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
When you need a component that this table doesn't list, check `packages/bluesea/src/index.ts` for the full export list before assuming it doesn't exist. Use the demo pages under `packages/bluesea-demo/src/page/guide/BS*Guide.vue` as copy-paste-ready examples — they are the canonical "how do I use this component" reference.
|
|
142
|
+
|
|
143
|
+
## 4. Modals and notifications
|
|
144
|
+
|
|
145
|
+
The modal plugin is *the* way to open overlays. `useModal()` gives you `openModal`, `openAlert`, `openYesNo`; each returns a `RegisteredModalItem` whose `modalHandle.close()` programmatically closes it. Modals are plain Vue components — pass props via `bind`, listen to events via `on`.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { useModal, showNotification, withLoading } from '@g1cloud/bluesea'
|
|
149
|
+
|
|
150
|
+
const modal = useModal()
|
|
151
|
+
|
|
152
|
+
modal.openAlert('알림', '저장되었습니다.')
|
|
153
|
+
|
|
154
|
+
modal.openYesNo('삭제', '정말 삭제할까요?',
|
|
155
|
+
async () => { await api.delete(id) },
|
|
156
|
+
() => { /* cancelled */ })
|
|
157
|
+
|
|
158
|
+
// custom component
|
|
159
|
+
const handle = modal.openModal({
|
|
160
|
+
component: EditUserModal,
|
|
161
|
+
style: { width: '600px', resizable: true },
|
|
162
|
+
bind: { userId: 123 },
|
|
163
|
+
on: { submit: (user) => { console.log(user); handle.modalHandle.close() } },
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Inside the modal component, call `useModalHandle()` to get the same handle (`handle.close()`, `setDefaultStyle()`, etc.).
|
|
168
|
+
|
|
169
|
+
Toast-style messages go through `showNotification(message, style, durationMs)` where `style` is `'info' | 'error'`. Longer-lived "alarm" overlays use `showAlarm(component, duration)`. For a global spinner during async work:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
await withLoading(async () => {
|
|
173
|
+
await api.save(form)
|
|
174
|
+
showNotification({ key: 'msg.saved' })
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 5. Validation (the most distinctive Bluesea pattern)
|
|
179
|
+
|
|
180
|
+
Every input that takes a `name` prop auto-registers a `FieldValidator` on its DOM element. A `FormValidator` walks the DOM of a given root element, collects every registered `FieldValidator`, and runs validation. This means you do **not** wire up validators manually per field — you just give each field a `name` and scope a `FormValidator` to your form root.
|
|
181
|
+
|
|
182
|
+
```vue
|
|
183
|
+
<template>
|
|
184
|
+
<div ref="formEl">
|
|
185
|
+
<BSTextInput v-model="form.email" name="email" required
|
|
186
|
+
reg-exp="^[^@]+@[^@]+$"
|
|
187
|
+
:validation-message-reg-exp="{ key: 'err.email' }" />
|
|
188
|
+
<BSNumberInput v-model="form.age" name="age" :min-value="0" :max-value="150" />
|
|
189
|
+
<BSButton caption="저장" @click="save" />
|
|
190
|
+
</div>
|
|
191
|
+
</template>
|
|
192
|
+
|
|
193
|
+
<script setup lang="ts">
|
|
194
|
+
import { ref } from 'vue'
|
|
195
|
+
import { formValidator, ValidationFailedError, isValidationFailedError, showNotification } from '@g1cloud/bluesea'
|
|
196
|
+
|
|
197
|
+
const formEl = ref<HTMLElement>()
|
|
198
|
+
const form = ref({ email: '', age: 0 })
|
|
199
|
+
const validator = formValidator({
|
|
200
|
+
element: formEl,
|
|
201
|
+
rules: [
|
|
202
|
+
async () => form.value.age < 18
|
|
203
|
+
? [{ code: 'tooYoung', message: { key: 'err.adultOnly' } }]
|
|
204
|
+
: undefined,
|
|
205
|
+
],
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
async function save() {
|
|
209
|
+
try {
|
|
210
|
+
await validator.validate() // throws ValidationFailedError on failure
|
|
211
|
+
await api.save(form.value)
|
|
212
|
+
showNotification({ key: 'msg.saved' })
|
|
213
|
+
} catch (e) {
|
|
214
|
+
if (isValidationFailedError(e)) {
|
|
215
|
+
showNotification({ key: 'err.validationFailed' }, 'error')
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
throw e
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
</script>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Built-in rules on inputs: `required`, `minLength` / `maxLength`, `regExp`, `minValue` / `maxValue`, `validation-message-*` overrides for each rule. For custom rules, pass `:extra-validation-rules` — an array of `(value, phase, fieldContext) => ValidationError[] | undefined`. `phase` is `'input' | 'change' | 'blur' | 'form'`, so you can defer expensive async checks to `'blur'` or `'form'` only.
|
|
225
|
+
|
|
226
|
+
`ValidationError` is `{ code: string; message: MultiLangText }`. `ValidationFailedError.errors` is an array that includes the field `name`, so you can highlight specific fields if needed.
|
|
227
|
+
|
|
228
|
+
## 6. BSGrid
|
|
229
|
+
|
|
230
|
+
BSGrid is the workhorse for data tables. Prefer the `createPageGridHandler` factory when you need paging + server-side sorting/filtering — it wires `grid`, `gridEventListener`, `control`, `lookup`, `lookupEventListener` together for you. For simple client-side tables, use `BSGrid` directly with `:columns` + `:data`.
|
|
231
|
+
|
|
232
|
+
```vue
|
|
233
|
+
<BSGridLookup v-bind="gridHandler.lookup" v-on="gridHandler.lookupEventListener" :config="lookupConfig" />
|
|
234
|
+
<BSGridControl v-bind="gridHandler.control" v-on="gridHandler.controlEventListener" />
|
|
235
|
+
<BSGrid v-bind="gridHandler.grid" v-on="gridHandler.gridEventListener">
|
|
236
|
+
<template #name="{ row }"><strong>{{ row.name }}</strong></template>
|
|
237
|
+
<template #status="{ row }"><span class="badge">{{ row.status }}</span></template>
|
|
238
|
+
<template #price.edit="{ row }"><BSNumberInput v-model="row.price" name="price" /></template>
|
|
239
|
+
</BSGrid>
|
|
240
|
+
|
|
241
|
+
<script setup lang="ts">
|
|
242
|
+
import { createPageGridHandler, type Column, type PaginatedList } from '@g1cloud/bluesea'
|
|
243
|
+
|
|
244
|
+
type User = { id: number; name: string; email: string; status: string; price?: number }
|
|
245
|
+
|
|
246
|
+
const columns: Column<User>[] = [
|
|
247
|
+
{ propertyId: 'name', caption: { key: 'col.name' }, width: 160, sortable: true },
|
|
248
|
+
{ propertyId: 'email', caption: { key: 'col.email' }, width: 220 },
|
|
249
|
+
{ propertyId: 'price', caption: '가격', width: 120, cellType: 'NUMBER' },
|
|
250
|
+
{ propertyId: 'status', caption: '상태', width: 100 },
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
const gridHandler = createPageGridHandler<User>({
|
|
254
|
+
gridId: 'user-list', // used by gridPreferenceStore to remember column widths
|
|
255
|
+
getRowKey: (u) => String(u.id),
|
|
256
|
+
editable: true,
|
|
257
|
+
getGridData: async (param): Promise<PaginatedList<User>> => api.users.search(param),
|
|
258
|
+
defaultSorts: [{ property: 'name', direction: 'ASC' }],
|
|
259
|
+
limit: 100,
|
|
260
|
+
limitItems: [100, 300, 500],
|
|
261
|
+
})
|
|
262
|
+
gridHandler.grid.columnSettings = columns.map(c => ({ propertyId: c.propertyId, hidden: false }))
|
|
263
|
+
await gridHandler.loadGridData()
|
|
264
|
+
</script>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Cell customisation uses named slots: `#<propertyId>` for display, `#<propertyId>.edit` for the editor when `editable: true` and the row is in `editingRows`. Use `cellType` to get built-in formatting (`'TEXT' | 'NUMBER' | 'DATE' | 'PERCENTAGE' | 'BOOL' | 'MULTI_LANG_STRING' | 'NAME' | 'TEL' | 'ADDRESS' | 'MONEY'`). For date columns also set `dateFormat: 'DAY' | 'MINUTE' | 'SECOND'` or a dayjs format string.
|
|
268
|
+
|
|
269
|
+
For deeper grid topics (inline add/remove, filters, Excel export, fixed columns, extensions) read the `BSGrid` section in `references/grid.md` and the guide page `packages/bluesea-demo/src/page/guide/BSGridGuide.vue`.
|
|
270
|
+
|
|
271
|
+
## 7. SavePoint (inline-edit modified state)
|
|
272
|
+
|
|
273
|
+
SavePoint lets a form remember its "saved" state and surface a `modified` flag per field. It is automatically injected by `BSTabSheet` and available via `useSavePoint()` — most inputs (`BSTextInput`, etc.) register themselves so the "modified" dot appears without any extra code. You typically only interact with it to `set()` after a successful save or `rollback()` on cancel:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { useSavePoint } from '@g1cloud/bluesea'
|
|
277
|
+
const savePoint = useSavePoint()
|
|
278
|
+
|
|
279
|
+
async function save() { await api.save(form.value); savePoint?.set() }
|
|
280
|
+
function cancel() { savePoint?.rollback() }
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Pass `:ignore-save-point="true"` on an input to opt it out of modified-tracking.
|
|
284
|
+
|
|
285
|
+
## 8. Directives
|
|
286
|
+
|
|
287
|
+
- `v-t="multiLangText"` — set element textContent from a MultiLangText.
|
|
288
|
+
- `v-t.placeholder="multiLangText"` — set the `placeholder` attribute (or any attribute via the modifier name).
|
|
289
|
+
- `v-tooltip="{ content: multiLangText }"` — show on hover.
|
|
290
|
+
- `v-click-outside="handler"` — fire when the user clicks outside the element.
|
|
291
|
+
- `v-focus-on-load`, `v-focus-jump`, `v-focus-loop` — focus management helpers for forms.
|
|
292
|
+
|
|
293
|
+
Register `vT` globally (`.directive('t', vT)`). The others can be imported and used ad-hoc.
|
|
294
|
+
|
|
295
|
+
## 9. DefaultFrame (BackOffice app shell)
|
|
296
|
+
|
|
297
|
+
If the app is a BackOffice application, use `DefaultHeader` + `DefaultBody` for sidebar + tab management. Build a `FrameModel` with your menu tree and tab controller. This is an opinionated shell — skip it for standalone screens. Detailed wiring lives in `references/frame.md` and the `DefaultFrameGuide` demo page.
|
|
298
|
+
|
|
299
|
+
## When to write code vs read the source
|
|
300
|
+
|
|
301
|
+
- For a component's exact prop set, **read the `.vue` file** under `packages/bluesea/src/component/`. The `defineProps<{...}>()` block is authoritative; what a docs page says may lag behind.
|
|
302
|
+
- For a real-world wiring example, **open the matching `BS*Guide.vue`** in `packages/bluesea-demo/src/page/guide/`. They cover the common scenarios end-to-end.
|
|
303
|
+
- For types (`MultiLangText`, `StoredFile`, `Money`, `Filter`, `Sort`, `PaginatedList`, `Column`, etc.), the model files in `packages/bluesea/src/model/` and `packages/bluesea/src/component/grid/GridModel.ts` are short and worth reading directly.
|
|
304
|
+
|
|
305
|
+
## Reference files
|
|
306
|
+
|
|
307
|
+
- `references/components.md` — fuller prop reference for the most-used components, grouped by category.
|
|
308
|
+
- `references/grid.md` — BSGrid deep-dive: PageGridHandler, filters, inline editing, extensions, Excel export.
|
|
309
|
+
- `references/validation.md` — custom field rules, form-level rules, async validation, and error mapping.
|
|
310
|
+
- `references/i18n.md` — i18n setup, MultiLangText patterns, and locale switching.
|
|
311
|
+
|
|
312
|
+
Load a reference file only when the user's question clearly touches that area; keep this file as the default map.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Bluesea components — detailed prop reference
|
|
2
|
+
|
|
3
|
+
Use this as a prop lookup. When in doubt read the `.vue` file — the `defineProps<{...}>()` block is always the ground truth. Paths below are relative to `packages/bluesea/src/component/`.
|
|
4
|
+
|
|
5
|
+
## Shared conventions
|
|
6
|
+
|
|
7
|
+
Most inputs share this surface — only deltas are listed per component below.
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Notes |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `v-model` | matches cell type | Two-way binding. |
|
|
12
|
+
| `name` | `string` | Required for validation + SavePoint tracking. Auto-generated when omitted, but explicit names are easier to debug. |
|
|
13
|
+
| `disabled` | `boolean` | Also disables validation unless `forceValidateWhenDisabled` is set. |
|
|
14
|
+
| `required` | `boolean` | Enables built-in "required" rule. |
|
|
15
|
+
| `viewMode` | `boolean` | Renders as read-only text (no `<input>`). |
|
|
16
|
+
| `placeholder` | `MultiLangText` | |
|
|
17
|
+
| `width` | CSS size string | Default `'200px'` on text/number inputs. |
|
|
18
|
+
| `tabindex` | `number` | |
|
|
19
|
+
| `hideErrorMessage` | `boolean` | Suppress the inline error `<span>`. |
|
|
20
|
+
| `showErrorMessageOnDisabled` | `boolean` | Show errors even when disabled. |
|
|
21
|
+
| `ignoreSavePoint` | `boolean` | Opt out of SavePoint "modified" tracking. |
|
|
22
|
+
| `extraValidationRules` | `FieldValidationRule<T>[]` | Custom rules appended after built-ins. |
|
|
23
|
+
| `validationMessage*` | `MultiLangText` | Overrides for each built-in rule's error message. |
|
|
24
|
+
|
|
25
|
+
## Basic
|
|
26
|
+
|
|
27
|
+
### BSButton (`basic/BSButton.vue`)
|
|
28
|
+
Renders as `<button>`, `<a>`, or `<router-link>` depending on props.
|
|
29
|
+
|
|
30
|
+
- `caption: string | MultiLangText`
|
|
31
|
+
- `buttonColor: 'default' | 'blue' | 'red' | 'orange' | 'green' | 'gray' | 'underline'` (default `'default'`)
|
|
32
|
+
- `leftIcon`, `rightIcon: string` — font-icon ligature
|
|
33
|
+
- `disabled: boolean`
|
|
34
|
+
- `linkUrl: string`, `linkTarget: string` — renders `<a>`
|
|
35
|
+
- `routePath: string` — renders `<router-link :to>`
|
|
36
|
+
- `tooltip: MultiLangText`
|
|
37
|
+
|
|
38
|
+
### BSFormLabel (`basic/BSFormLabel.vue`)
|
|
39
|
+
- `label: MultiLangText`
|
|
40
|
+
- `required: boolean`
|
|
41
|
+
- `tooltip: MultiLangText`
|
|
42
|
+
|
|
43
|
+
### BSLink, BSImage, BSPopup, BSPopupButton, BSProgressBar, BSLoadingIcon, BSDate, BSCalendar, BSCalendarRange, BSPageNavigation, BSConsole
|
|
44
|
+
Read the individual files — they are short and each has a targeted purpose.
|
|
45
|
+
|
|
46
|
+
## Text / number / money
|
|
47
|
+
|
|
48
|
+
### BSTextInput (`input/BSTextInput.vue`)
|
|
49
|
+
`v-model` is `string`.
|
|
50
|
+
|
|
51
|
+
- `inputType: 'text' | 'password'`
|
|
52
|
+
- `maxlength: number` — HTML attribute
|
|
53
|
+
- `minLength: number`, `maxLength: number` — validation rules
|
|
54
|
+
- `regExp: string`
|
|
55
|
+
- `trimValue: boolean` (default `true`) — trims before emit
|
|
56
|
+
- `prefix`, `suffix: MultiLangText | MultiLangText[] | PrefixSuffix | PrefixSuffix[]` — decoration in the input border
|
|
57
|
+
- `autocomplete: string`
|
|
58
|
+
|
|
59
|
+
### BSNumberInput, BSPriceInput, BSPercentInput (`input/BSNumberInput.vue`, etc.)
|
|
60
|
+
`v-model` is `number | undefined`.
|
|
61
|
+
|
|
62
|
+
- `minValue`, `maxValue: number`
|
|
63
|
+
- `decimalPlace: number`
|
|
64
|
+
- `thousandSeparator: boolean`
|
|
65
|
+
- `unit: MultiLangText` — e.g. `'원'`, `'%'`
|
|
66
|
+
- `currencyCode: CurrencyCode` — on `BSPriceInput`; falls back to `blueseaConfig.defaultCurrencyCode`
|
|
67
|
+
|
|
68
|
+
### BSTextArea (`input/BSTextArea.vue`)
|
|
69
|
+
`v-model` is `string`. Adds `rows: number`, `autoResize: boolean`.
|
|
70
|
+
|
|
71
|
+
### BSColorInput (`input/BSColorInput.vue`)
|
|
72
|
+
`v-model` is a hex string like `'#RRGGBB'`.
|
|
73
|
+
|
|
74
|
+
## Date / time
|
|
75
|
+
|
|
76
|
+
### BSDateInput, BSDateRange (`input/BSDateInput.vue`, `BSDateRange.vue`)
|
|
77
|
+
`v-model` is an ISO string (`dayjs`-parseable).
|
|
78
|
+
|
|
79
|
+
- `dateFormat: string` — dayjs format; falls back to `blueseaConfig.dateFormat`
|
|
80
|
+
- `timeZone: TimeZone` — falls back to `blueseaConfig.timeZone`
|
|
81
|
+
- `resolution: DateResolution` — `'DAY' | 'HOUR' | 'MINUTE' | 'MINUTE_10' | 'MINUTE_30' | 'SECOND'`
|
|
82
|
+
- `minValue`, `maxValue: string` — ISO bounds
|
|
83
|
+
|
|
84
|
+
Range version emits two values via `v-model:from` / `v-model:to`, or via a `DateRange` object depending on flavor; check the component.
|
|
85
|
+
|
|
86
|
+
### BSCalendar, BSCalendarRange
|
|
87
|
+
Embedded calendars; useful inside custom popups.
|
|
88
|
+
|
|
89
|
+
## Selection
|
|
90
|
+
|
|
91
|
+
### BSSelect (`input/BSSelect.vue`)
|
|
92
|
+
Generic: `<BSSelect :items="items" :key-provider="..." :label-provider="..." />`.
|
|
93
|
+
|
|
94
|
+
- `items: T[]`
|
|
95
|
+
- `keyProvider: (item: T) => string | undefined`
|
|
96
|
+
- `labelProvider: (item: T) => MultiLangText | undefined`
|
|
97
|
+
- `iconProvider`, `tooltipProvider`, `enabledItemProvider`
|
|
98
|
+
- `allowEmpty: boolean`, `emptyLabel: MultiLangText`
|
|
99
|
+
- `filterable: boolean` — show search box in popup
|
|
100
|
+
|
|
101
|
+
### BSMultiSelect, BSTreeSelect, BSTreeMultiSelect, BSPopupSelect
|
|
102
|
+
Same general shape; multi-select uses `Set<T>` or `T[]`.
|
|
103
|
+
|
|
104
|
+
### BSCheckbox, BSCheckboxGroup, BSRadioButton, BSRadioButtonGroup, BSYesNoSelect, BSYesNoGroup
|
|
105
|
+
Check the `defineProps` block; `items` + `keyProvider` + `labelProvider` pattern is consistent.
|
|
106
|
+
|
|
107
|
+
## Upload
|
|
108
|
+
|
|
109
|
+
### BSImageUpload (`input/BSImageUpload.vue`), BSFileUpload, BSMultiImageUpload, BSPositionedImageUpload
|
|
110
|
+
`v-model` is `StoredFile | StoredFile[]`.
|
|
111
|
+
|
|
112
|
+
Key props:
|
|
113
|
+
- `maxFileSize: number` (falls back to `blueseaConfig.maxFileSize`)
|
|
114
|
+
- `accept: string` — mime filter
|
|
115
|
+
- `preloadOverlay: boolean` — preload preview overlay image (recently renamed from `preloadPreview`)
|
|
116
|
+
- Upload handlers come from your backend; Bluesea itself doesn't own the upload URL. You store the resulting URL on the `StoredFile.fileUrl` when the upload succeeds.
|
|
117
|
+
|
|
118
|
+
## Multi-language inputs
|
|
119
|
+
|
|
120
|
+
`BSMultiLangTextInput`, `BSMultiLangTextArea`, `BSMultiLangRichText`, `BSMultiLangHtmlEditor`, `BSMultiLangImageUpload` wrap their single-language counterparts and bind to a `MultiLangString` (or `MultiLangStoredFile`) keyed by `dataLocales` from config. They render one input per locale with a language selector.
|
|
121
|
+
|
|
122
|
+
## Rich text / code
|
|
123
|
+
|
|
124
|
+
### BSRichText (`richtext/BSRichText.vue`)
|
|
125
|
+
TipTap-based WYSIWYG.
|
|
126
|
+
|
|
127
|
+
- `v-model` — HTML string
|
|
128
|
+
- `toolButtons: ToolButton[]` — subset of `'Heading' | 'FontSize' | 'FontColor' | 'FontStyle' | 'TextAlign' | 'ListItem' | 'Link' | 'Table' | 'Image' | 'Video' | 'Youtube'`
|
|
129
|
+
- `imageInsertModal`, `videoInsertModal: Component` — override default modals
|
|
130
|
+
|
|
131
|
+
### BSHtmlEditor (`input/BSHtmlEditor.vue`)
|
|
132
|
+
Split view (source + preview) for raw HTML.
|
|
133
|
+
|
|
134
|
+
### BSCodeEditor (`input/BSCodeEditor.vue`)
|
|
135
|
+
CodeMirror 6 — props `language`, `theme`, `readonly`.
|
|
136
|
+
|
|
137
|
+
## Layout
|
|
138
|
+
|
|
139
|
+
### BSCardLayout (`layout/BSCardLayout.vue`)
|
|
140
|
+
Container with header/footer slots.
|
|
141
|
+
|
|
142
|
+
### BSListLayout + BSListControl (`layout/BSListLayout.vue`)
|
|
143
|
+
Master-detail layout with resizable panes.
|
|
144
|
+
|
|
145
|
+
### BSTabSheet (`layout/BSTabSheet.vue`)
|
|
146
|
+
Manages tabs, provides a SavePoint to each tab. Config toggles `blockLeavingModifiedTab` / `confirmLeavingModifiedTab` via global `componentConfig.tabSheet`.
|
|
147
|
+
|
|
148
|
+
### BSHorizontalLayoutResizer, BSVerticalLayoutResizer
|
|
149
|
+
Drag-to-resize between two DOM siblings.
|
|
150
|
+
|
|
151
|
+
## Tree
|
|
152
|
+
|
|
153
|
+
### BSTree (`tree/BSTree.vue`)
|
|
154
|
+
- `items: T[]`, `childrenProvider: (item: T) => T[] | undefined`
|
|
155
|
+
- `keyProvider`, `labelProvider`, `iconProvider`
|
|
156
|
+
- `draggable: boolean`
|
|
157
|
+
- `selection: Set<T>` via `v-model:selected`
|
|
158
|
+
- Scoped slot `#item="{ item }"` to customise rendering
|
|
159
|
+
|
|
160
|
+
### BSTreeControl
|
|
161
|
+
Toolbar for BSTree (add / remove / expand-all).
|
|
162
|
+
|
|
163
|
+
## Global entity inputs
|
|
164
|
+
|
|
165
|
+
### BSNameInput, BSTelInput, BSAddressInput
|
|
166
|
+
Bind to `Name`, `Tel`, `Address` models from `@/model/CommonTypes`. Layout (number of input boxes, widths, line breaks) comes from `componentConfig.nameInput / telInput / addressInput`, so configure them once globally.
|
|
167
|
+
|
|
168
|
+
### BSLocaleSelect
|
|
169
|
+
Dropdown that switches `blueseaConfig.currentLocale`. Handy for a language toggle in the header.
|
|
170
|
+
|
|
171
|
+
## Popup
|
|
172
|
+
|
|
173
|
+
### BSPopup, BSPopupButton, BSSelectPopup, BSDateInputPopup, BSDateRangeInputPopup
|
|
174
|
+
The popup system anchors a component to a target element. `BSPopup` is the low-level primitive — pass `target`, `open`, `position`. Most selection components already wrap `BSPopup` internally; reach for it yourself only for custom pickers.
|
|
175
|
+
|
|
176
|
+
## Grid
|
|
177
|
+
|
|
178
|
+
See `references/grid.md`. Top-level exports: `BSGrid`, `BSGridLookup`, `BSGridControl`, plus types `Column`, `EditingRows`, `PageGridHandler`, `createPageGridHandler`.
|
|
179
|
+
|
|
180
|
+
## Notification / modal / context menu containers
|
|
181
|
+
|
|
182
|
+
- `BSModalContainer` — mount once; the modal plugin renders into it.
|
|
183
|
+
- `BSNotificationContainer` — mount once; drives `showNotification`, `showAlarm`, `showLoading`.
|
|
184
|
+
- `BSContextMenuContainer` — mount once; the context-menu plugin renders into it.
|
|
185
|
+
|
|
186
|
+
## Frame
|
|
187
|
+
|
|
188
|
+
- `DefaultHeader`, `DefaultBody` — BackOffice app shell.
|
|
189
|
+
- `BSAlarmFrame` — standalone alarm container if you don't use DefaultFrame.
|