@h-ai/ui 0.1.0-alpha.61 → 0.1.0-alpha.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.en.md +93 -8
  2. package/README.ja.md +93 -8
  3. package/README.md +94 -8
  4. package/auto-import.d.ts +3 -0
  5. package/auto-import.js +3 -0
  6. package/dist/components/compounds/Combobox.svelte +19 -6
  7. package/dist/components/compounds/Combobox.svelte.d.ts +2 -2
  8. package/dist/components/compounds/DatePicker.svelte +14 -5
  9. package/dist/components/compounds/DatePicker.svelte.d.ts +2 -2
  10. package/dist/components/compounds/Form.svelte +10 -6
  11. package/dist/components/compounds/FormField.svelte +78 -13
  12. package/dist/components/compounds/FormLayout.svelte +59 -0
  13. package/dist/components/compounds/FormLayout.svelte.d.ts +4 -0
  14. package/dist/components/compounds/TagInput.svelte +1 -1
  15. package/dist/components/compounds/index.d.ts +1 -0
  16. package/dist/components/compounds/index.js +1 -0
  17. package/dist/components/primitives/BareInput.svelte +7 -1
  18. package/dist/components/primitives/Checkbox.svelte +8 -2
  19. package/dist/components/primitives/Input.svelte +22 -11
  20. package/dist/components/primitives/Select.svelte +77 -18
  21. package/dist/components/primitives/Textarea.svelte +27 -13
  22. package/dist/components/primitives/Textarea.svelte.d.ts +1 -1
  23. package/dist/components/scenes/app/FeedbackModal.svelte +29 -19
  24. package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +2 -2
  25. package/dist/components/scenes/crud/CrudEditPanel.svelte +148 -118
  26. package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +4 -25
  27. package/dist/components/scenes/crud/CrudPage.svelte +5 -41
  28. package/dist/components/scenes/crud/CrudPage.svelte.d.ts +2 -53
  29. package/dist/components/scenes/crud/crud-controller.svelte.d.ts +4 -1
  30. package/dist/components/scenes/crud/crud-form-validation.d.ts +4 -0
  31. package/dist/components/scenes/crud/crud-form-validation.js +39 -0
  32. package/dist/components/scenes/crud/crud-types.d.ts +9 -42
  33. package/dist/components/scenes/guide/GuideHost.svelte +111 -0
  34. package/dist/components/scenes/guide/GuideHost.svelte.d.ts +16 -0
  35. package/dist/components/scenes/guide/HelpCenter.svelte +94 -0
  36. package/dist/components/scenes/guide/HelpCenter.svelte.d.ts +13 -0
  37. package/dist/components/scenes/guide/index.d.ts +2 -0
  38. package/dist/components/scenes/guide/index.js +2 -0
  39. package/dist/components/scenes/iam/AuthPanel.svelte +55 -47
  40. package/dist/components/scenes/iam/ChangePasswordForm.svelte +57 -59
  41. package/dist/components/scenes/iam/ForgotPasswordForm.svelte +89 -84
  42. package/dist/components/scenes/iam/LoginForm.svelte +134 -132
  43. package/dist/components/scenes/iam/PasswordInput.svelte +25 -7
  44. package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +2 -2
  45. package/dist/components/scenes/iam/RegisterForm.svelte +95 -97
  46. package/dist/components/scenes/iam/ResetPasswordForm.svelte +83 -85
  47. package/dist/components/scenes/iam/UserProfile.svelte +9 -5
  48. package/dist/components/scenes/iam/auth-error-message.d.ts +2 -4
  49. package/dist/components/scenes/index.d.ts +1 -0
  50. package/dist/components/scenes/index.js +1 -0
  51. package/dist/components/scenes/storage/FileUpload.svelte +1 -1
  52. package/dist/components/scenes/storage/ImageUpload.svelte +1 -0
  53. package/dist/components/scenes/types.d.ts +9 -9
  54. package/dist/guide-storage.d.ts +6 -0
  55. package/dist/guide-storage.js +67 -0
  56. package/dist/guide-types.d.ts +78 -0
  57. package/dist/guide-types.js +1 -0
  58. package/dist/guide.d.ts +3 -0
  59. package/dist/guide.js +3 -0
  60. package/dist/guide.svelte.d.ts +8 -0
  61. package/dist/guide.svelte.js +450 -0
  62. package/dist/i18n.svelte.d.ts +2 -1
  63. package/dist/i18n.svelte.js +2 -1
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +2 -0
  66. package/dist/internal/form-context.d.ts +12 -0
  67. package/dist/internal/form-context.js +9 -0
  68. package/dist/internal/form-validation.svelte.d.ts +16 -0
  69. package/dist/internal/form-validation.svelte.js +76 -0
  70. package/dist/internal/locale.d.ts +2 -0
  71. package/dist/internal/locale.js +22 -0
  72. package/dist/messages/en-US.json +30 -1
  73. package/dist/messages/zh-CN.json +30 -1
  74. package/dist/messages.d.ts +2 -1
  75. package/dist/messages.js +6 -4
  76. package/dist/styles/guide.css +88 -0
  77. package/dist/types.d.ts +57 -10
  78. package/package.json +13 -2
  79. package/vite.js +1 -0
@@ -10,7 +10,10 @@
10
10
  -->
11
11
  <script lang='ts' generics="T = string">
12
12
  import type { DataAttributes, SelectProps } from '../../types.js'
13
- import { tick } from 'svelte'
13
+ import { tick, untrack } from 'svelte'
14
+ import { getFormFieldContext } from '../../internal/form-context.js'
15
+ import { createControlValidation, resolveFormText } from '../../internal/form-validation.svelte.js'
16
+ import { uiM } from '../../messages.js'
14
17
  import { cn, getDataAttributes, portal } from '../../utils.js'
15
18
  import { getFormControlSizeClasses } from '../control-size.js'
16
19
 
@@ -24,13 +27,38 @@
24
27
  clearable = false,
25
28
  filterable = false,
26
29
  error = '',
30
+ validationMessage = '',
27
31
  class: className = '',
28
32
  id,
33
+ name,
29
34
  onchange,
30
35
  ...restProps
31
36
  }: SelectProps<T> & DataAttributes = $props()
32
37
 
33
38
  const dataAttributes = $derived(getDataAttributes(restProps))
39
+ const field = getFormFieldContext()
40
+ const uniqueId = $props.id()
41
+ const controlId = $derived(id ?? field?.controlId ?? `${uniqueId}-control`)
42
+ const messageId = `${uniqueId}-error`
43
+ const validation = createControlValidation(() => validationMessage)
44
+ const localError = $derived(resolveFormText(error) || validation.message)
45
+ const effectiveError = $derived(field?.error || localError)
46
+ let nativeSelect: HTMLSelectElement | undefined = $state()
47
+ // 独立保存原生选中值,筛选输入的文字不能代替表单实际选项。
48
+ const nativeValue = $derived(value === undefined || value === null ? '' : String(value))
49
+ $effect(() => {
50
+ void options
51
+ void required
52
+ void disabled
53
+ if (nativeSelect) {
54
+ nativeSelect.value = nativeValue
55
+ untrack(() => validation.input(nativeSelect!))
56
+ }
57
+ })
58
+ $effect(() => {
59
+ if (nativeSelect)
60
+ nativeSelect.setCustomValidity(validation.message)
61
+ })
34
62
  let inputRef: HTMLInputElement | undefined = $state()
35
63
  let dropdownRef: HTMLDivElement | undefined = $state()
36
64
  let triggerRef: HTMLDivElement | undefined = $state()
@@ -58,7 +86,7 @@
58
86
 
59
87
  // 获取当前选中的标签
60
88
  const selectedLabel = $derived(() => {
61
- if (!options || !value)
89
+ if (!options || value === undefined || value === null)
62
90
  return ''
63
91
  const selected = options.find(opt => opt.value === value)
64
92
  return selected ? selected.label : ''
@@ -79,7 +107,7 @@
79
107
  cn(
80
108
  'relative flex w-full min-w-0 items-center rounded-lg border bg-base-100 cursor-pointer',
81
109
  sizeClasses.control,
82
- error
110
+ effectiveError
83
111
  ? 'border-error/60 focus-within:ring-2 focus-within:ring-error/15'
84
112
  : isDropdownOpen
85
113
  ? 'border-primary/50 ring-2 ring-primary/10'
@@ -95,7 +123,7 @@
95
123
  'flex-1 min-w-0 bg-transparent outline-none border-none',
96
124
  'focus:outline-none focus:ring-0 focus:shadow-none',
97
125
  'placeholder:text-base-content/40',
98
- error && 'text-error',
126
+ effectiveError && 'text-error',
99
127
  ),
100
128
  )
101
129
 
@@ -238,12 +266,31 @@
238
266
  {...dataAttributes}
239
267
  class='fieldset w-full min-w-0'
240
268
  bind:this={dropdownRef}
241
- role='combobox'
242
- aria-expanded={isDropdownOpen}
243
- aria-haspopup='listbox'
244
269
  onmouseenter={() => isHovered = true}
245
270
  onmouseleave={() => isHovered = false}
246
271
  >
272
+ <select
273
+ bind:this={nativeSelect}
274
+ class='sr-only'
275
+ tabindex='-1'
276
+ aria-hidden='true'
277
+ {name}
278
+ {required}
279
+ {disabled}
280
+ value={nativeValue}
281
+ oninvalid={(e) => {
282
+ e.preventDefault()
283
+ validation.invalid(e.currentTarget)
284
+ inputRef?.focus()
285
+ }}
286
+ >
287
+ <option value=''></option>
288
+ {#each options ?? [] as option (String(option.value))}
289
+ {#if String(option.value) !== ''}
290
+ <option value={String(option.value)} disabled={option.disabled}>{option.label}</option>
291
+ {/if}
292
+ {/each}
293
+ </select>
247
294
  <div
248
295
  class={wrapperClass}
249
296
  bind:this={triggerRef}
@@ -260,11 +307,12 @@
260
307
  <!-- 可筛选模式:可输入的 input -->
261
308
  <input
262
309
  bind:this={inputRef}
263
- {id}
310
+ id={controlId}
311
+ aria-invalid={effectiveError ? true : undefined}
312
+ aria-describedby={[field?.messageId, localError && !field?.error ? messageId : ''].filter(Boolean).join(' ') || undefined}
264
313
  type='text'
265
314
  class={inputClass}
266
315
  {disabled}
267
- {required}
268
316
  {placeholder}
269
317
  value={isDropdownOpen ? filterText : selectedLabel()}
270
318
  oninput={handleFilterInput}
@@ -274,22 +322,33 @@
274
322
  isDropdownOpen = true
275
323
  }}
276
324
  role='combobox'
325
+ aria-haspopup='listbox'
277
326
  aria-autocomplete='list'
278
- aria-controls='{id}-listbox'
327
+ aria-controls='{controlId}-listbox'
279
328
  aria-expanded={isDropdownOpen}
280
329
  />
281
330
  {:else}
282
331
  <!-- 普通模式:只读 input 显示选中值 -->
283
332
  <input
284
- {id}
333
+ bind:this={inputRef}
334
+ id={controlId}
335
+ aria-invalid={effectiveError ? true : undefined}
336
+ aria-describedby={[field?.messageId, localError && !field?.error ? messageId : ''].filter(Boolean).join(' ') || undefined}
285
337
  type='text'
286
338
  class={inputClass}
287
339
  readonly
288
340
  {disabled}
289
- {required}
290
341
  {placeholder}
291
342
  value={selectedLabel()}
292
- tabindex='-1'
343
+ onkeydown={handleInputKeydown}
344
+ onfocus={() => {
345
+ if (!disabled)
346
+ isDropdownOpen = true
347
+ }}
348
+ role='combobox'
349
+ aria-haspopup='listbox'
350
+ aria-controls='{controlId}-listbox'
351
+ aria-expanded={isDropdownOpen}
293
352
  />
294
353
  {/if}
295
354
 
@@ -301,7 +360,7 @@
301
360
  class='flex items-center justify-center w-5 h-5 rounded-full hover:bg-base-content/10 transition-colors'
302
361
  onclick={handleClear}
303
362
  tabindex='-1'
304
- aria-label='清空选择'
363
+ aria-label={uiM('select_clear')}
305
364
  >
306
365
  <svg xmlns='http://www.w3.org/2000/svg' class='h-3.5 w-3.5' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
307
366
  <path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 18L18 6M6 6l12 12' />
@@ -324,7 +383,7 @@
324
383
  <ul
325
384
  bind:this={listboxRef}
326
385
  use:portal
327
- id='{id}-listbox'
386
+ id='{controlId}-listbox'
328
387
  class='max-h-60 overflow-auto rounded-xl border border-base-content/10 bg-base-100 p-1.5 shadow-xl shadow-base-content/10'
329
388
  style={dropdownStyle}
330
389
  role='listbox'
@@ -356,13 +415,13 @@
356
415
  </li>
357
416
  {:else}
358
417
  <li class={cn('px-3 py-3 text-base-content/40 text-center', optionTextClass)}>
359
- 无匹配选项
418
+ {uiM('select_no_match')}
360
419
  </li>
361
420
  {/each}
362
421
  </ul>
363
422
  {/if}
364
423
  </div>
365
- {#if error}
366
- <span class='fieldset-label text-error'>{error}</span>
424
+ {#if localError && !field?.error}
425
+ <p id={messageId} class='mt-1.5 text-xs text-error leading-relaxed break-words' aria-live='polite'>{localError}</p>
367
426
  {/if}
368
427
  </div>
@@ -10,6 +10,8 @@
10
10
  -->
11
11
  <script lang='ts'>
12
12
  import type { DataAttributes, TextareaProps } from '../../types.js'
13
+ import { getFormFieldContext } from '../../internal/form-context.js'
14
+ import { createControlValidation, resolveFormText } from '../../internal/form-validation.svelte.js'
13
15
  import { cn, getDataAttributes, getInputSizeClass } from '../../utils.js'
14
16
 
15
17
  let {
@@ -23,20 +25,30 @@
23
25
  autoResize = false,
24
26
  error = '',
25
27
  validationMessage = '',
28
+ minlength,
29
+ maxlength,
30
+ inputmode,
31
+ textareaRef = $bindable<HTMLTextAreaElement | undefined>(),
26
32
  class: className = '',
27
33
  id,
28
34
  name,
29
35
  oninput,
36
+ oninvalid,
30
37
  ...restProps
31
38
  }: TextareaProps & DataAttributes = $props()
32
39
 
33
40
  const dataAttributes = $derived(getDataAttributes(restProps))
34
- let textareaRef: HTMLTextAreaElement | undefined = $state()
41
+ const field = getFormFieldContext()
42
+ const validation = createControlValidation(() => validationMessage, () => [value, required, readonly, disabled, minlength, maxlength])
43
+ const uniqueId = $props.id()
44
+ const messageId = `${uniqueId}-error`
45
+ const localError = $derived(resolveFormText(error) || validation.message)
46
+ const effectiveError = $derived(field?.error || localError)
35
47
 
36
48
  const wrapperClass = $derived(
37
49
  cn(
38
50
  'flex w-full rounded-lg border bg-base-100',
39
- error
51
+ effectiveError
40
52
  ? 'border-error/60 focus-within:ring-2 focus-within:ring-error/15'
41
53
  : 'border-base-content/15 focus-within:border-primary/50 focus-within:ring-2 focus-within:ring-primary/10',
42
54
  'transition-[border-color,box-shadow] duration-150',
@@ -48,7 +60,7 @@
48
60
  cn(
49
61
  'textarea w-full bg-transparent border-none outline-none',
50
62
  getInputSizeClass(size),
51
- error && 'textarea-error',
63
+ effectiveError && 'textarea-error',
52
64
  className,
53
65
  ),
54
66
  )
@@ -56,7 +68,7 @@
56
68
  // 当 validationMessage 变化时更新自定义验证消息
57
69
  $effect(() => {
58
70
  if (textareaRef) {
59
- textareaRef.setCustomValidity(validationMessage)
71
+ textareaRef.setCustomValidity(validation.message)
60
72
  }
61
73
  })
62
74
 
@@ -70,17 +82,14 @@
70
82
  }
71
83
 
72
84
  // 输入时重置自定义验证
73
- if (validationMessage) {
74
- e.currentTarget.setCustomValidity(validationMessage)
75
- }
85
+ validation.input(e.currentTarget)
76
86
 
77
87
  oninput?.(e)
78
88
  }
79
89
 
80
90
  function handleInvalid(e: Event & { currentTarget: HTMLTextAreaElement }) {
81
- if (validationMessage) {
82
- e.currentTarget.setCustomValidity(validationMessage)
83
- }
91
+ validation.invalid(e.currentTarget)
92
+ oninvalid?.(e)
84
93
  }
85
94
  </script>
86
95
 
@@ -88,20 +97,25 @@
88
97
  <div class={wrapperClass}>
89
98
  <textarea
90
99
  bind:this={textareaRef}
91
- {id}
100
+ id={id ?? field?.controlId}
101
+ aria-invalid={effectiveError ? true : undefined}
102
+ aria-describedby={[field?.messageId, localError && !field?.error ? messageId : ''].filter(Boolean).join(' ') || undefined}
92
103
  {name}
93
104
  {placeholder}
94
105
  {rows}
95
106
  {disabled}
96
107
  {readonly}
97
108
  {required}
109
+ {minlength}
110
+ {maxlength}
111
+ {inputmode}
98
112
  class={textareaClass}
99
113
  bind:value
100
114
  oninput={handleInput}
101
115
  oninvalid={handleInvalid}
102
116
  ></textarea>
103
117
  </div>
104
- {#if error}
105
- <span class='fieldset-label text-error'>{error}</span>
118
+ {#if localError && !field?.error}
119
+ <p id={messageId} class='mt-1.5 text-xs text-error leading-relaxed break-words' aria-live='polite'>{localError}</p>
106
120
  {/if}
107
121
  </div>
@@ -1,5 +1,5 @@
1
1
  import type { DataAttributes, TextareaProps } from '../../types.js';
2
2
  type $$ComponentProps = TextareaProps & DataAttributes;
3
- declare const Textarea: import("svelte").Component<$$ComponentProps, {}, "value">;
3
+ declare const Textarea: import("svelte").Component<$$ComponentProps, {}, "value" | "textareaRef">;
4
4
  type Textarea = ReturnType<typeof Textarea>;
5
5
  export default Textarea;
@@ -12,6 +12,8 @@
12
12
  />
13
13
  -->
14
14
  <script lang='ts'>
15
+ import FormField from '../../compounds/FormField.svelte'
16
+ import FormLayout from '../../compounds/FormLayout.svelte'
15
17
  import Modal from '../../compounds/Modal.svelte'
16
18
  import Button from '../../primitives/Button.svelte'
17
19
  import Input from '../../primitives/Input.svelte'
@@ -47,18 +49,18 @@
47
49
  }
48
50
  }
49
51
 
50
- import type { DataAttributes } from '../../../types.js'
52
+ import type { DataAttributes, FormLayoutOptions } from '../../../types.js'
51
53
  import { uiM } from '../../../messages.js'
52
54
  import { getDataAttributes } from '../../../utils.js'
53
55
 
54
- interface Props {
56
+ interface Props extends FormLayoutOptions {
55
57
  open?: boolean
56
58
  labels?: FeedbackLabels
57
59
  onsubmit?: (data: FeedbackData) => Promise<void>
58
60
  class?: string
59
61
  }
60
62
 
61
- let { open = $bindable(false), labels = {}, onsubmit, class: className = '', ...restProps }: Props & DataAttributes = $props()
63
+ let { open = $bindable(false), labels = {}, columns = 1, labelPosition = 'top', labelWidth = '8rem', onsubmit, class: className = '', ...restProps }: Props & DataAttributes = $props()
62
64
 
63
65
  const dataAttributes = $derived(getDataAttributes(restProps))
64
66
  // 文案优先使用传入的 labels,缺省回退到内置消息 uiM(...)
@@ -67,7 +69,9 @@
67
69
  let description = $state('')
68
70
  let contact = $state('')
69
71
  let loading = $state(false)
70
- let error = $state('')
72
+ let error = $state<'empty' | 'submit' | ''>('')
73
+ let submitError = $state('')
74
+ const errorText = $derived(error === 'empty' ? labels.errorEmpty ?? uiM('feedback_error_empty') : error === 'submit' ? submitError || labels.errorSubmit || uiM('feedback_error_submit') : '')
71
75
 
72
76
  const typeOptions = $derived([
73
77
  { value: 'bug', label: labels.types?.bug ?? uiM('feedback_type_bug') },
@@ -77,8 +81,10 @@
77
81
  ])
78
82
 
79
83
  async function handleSubmit() {
84
+ if (loading)
85
+ return
80
86
  if (!description.trim()) {
81
- error = labels.errorEmpty ?? uiM('feedback_error_empty')
87
+ error = 'empty'
82
88
  return
83
89
  }
84
90
 
@@ -95,7 +101,8 @@
95
101
  resetForm()
96
102
  }
97
103
  catch (e) {
98
- error = e instanceof Error ? e.message : (labels.errorSubmit ?? uiM('feedback_error_submit'))
104
+ submitError = e instanceof Error ? e.message : ''
105
+ error = 'submit'
99
106
  }
100
107
  finally {
101
108
  loading = false
@@ -107,41 +114,44 @@
107
114
  description = ''
108
115
  contact = ''
109
116
  error = ''
117
+ submitError = ''
110
118
  }
111
119
  </script>
112
120
 
113
121
  <Modal {...dataAttributes} class={className} bind:open title={labels.title ?? uiM('feedback_title')}>
114
122
  <p class='text-base-content/70 mb-4'>{labels.description ?? uiM('feedback_description')}</p>
115
123
 
116
- {#if error}
124
+ {#if error === 'submit'}
117
125
  <div class='alert alert-error mb-4'>
118
126
  <span class='icon-[tabler--alert-circle] size-5'></span>
119
- <span>{error}</span>
127
+ <span>{errorText}</span>
120
128
  </div>
121
129
  {/if}
122
130
 
123
- <div class='space-y-4'>
124
- <div class='fieldset'>
125
- <legend class='fieldset-legend'>{labels.typeLabel ?? uiM('feedback_type_label')}</legend>
131
+ <FormLayout {columns} {labelPosition} {labelWidth}>
132
+ <FormField for='feedback-type' label={labels.typeLabel ?? uiM('feedback_type_label')}>
126
133
  <Select
127
134
  id='feedback-type'
128
135
  options={typeOptions}
129
136
  bind:value={feedbackType}
130
137
  />
131
- </div>
138
+ </FormField>
132
139
 
133
- <div class='fieldset'>
134
- <legend class='fieldset-legend'>{labels.contentLabel ?? uiM('feedback_content_label')} <span class='text-error'>*</span></legend>
140
+ <FormField for='feedback-desc' label={labels.contentLabel ?? uiM('feedback_content_label')} required error={error === 'empty' ? errorText : ''}>
135
141
  <Textarea
136
142
  id='feedback-desc'
137
143
  class='h-32'
138
144
  placeholder={labels.contentPlaceholder ?? uiM('feedback_content_placeholder')}
139
145
  bind:value={description}
146
+ disabled={loading}
147
+ oninput={() => {
148
+ if (description.trim())
149
+ error = ''
150
+ }}
140
151
  />
141
- </div>
152
+ </FormField>
142
153
 
143
- <div class='fieldset'>
144
- <legend class='fieldset-legend'>{labels.contactLabel ?? uiM('feedback_contact_label')}</legend>
154
+ <FormField for='feedback-contact' label={labels.contactLabel ?? uiM('feedback_contact_label')}>
145
155
  <Input
146
156
  id='feedback-contact'
147
157
  type='text'
@@ -150,8 +160,8 @@
150
160
  bind:value={contact}
151
161
  autocomplete='email'
152
162
  />
153
- </div>
154
- </div>
163
+ </FormField>
164
+ </FormLayout>
155
165
 
156
166
  {#snippet footer()}
157
167
  <Button variant='ghost' onclick={() => open = false}>
@@ -1,4 +1,4 @@
1
- import type { DataAttributes } from '../../../types.js';
1
+ import type { DataAttributes, FormLayoutOptions } from '../../../types.js';
2
2
  type FeedbackType = 'bug' | 'feature' | 'question' | 'other';
3
3
  interface FeedbackData {
4
4
  type: FeedbackType;
@@ -25,7 +25,7 @@ interface FeedbackLabels {
25
25
  other?: string;
26
26
  };
27
27
  }
28
- interface Props {
28
+ interface Props extends FormLayoutOptions {
29
29
  open?: boolean;
30
30
  labels?: FeedbackLabels;
31
31
  onsubmit?: (data: FeedbackData) => Promise<void>;