@arqel-dev/fields 0.8.0
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/LICENSE +21 -0
- package/README.md +42 -0
- package/SKILL.md +123 -0
- package/dist/boolean.d.ts +8 -0
- package/dist/boolean.js +93 -0
- package/dist/boolean.js.map +1 -0
- package/dist/color.d.ts +6 -0
- package/dist/color.js +63 -0
- package/dist/color.js.map +1 -0
- package/dist/date.d.ts +8 -0
- package/dist/date.js +81 -0
- package/dist/date.js.map +1 -0
- package/dist/file.d.ts +8 -0
- package/dist/file.js +135 -0
- package/dist/file.js.map +1 -0
- package/dist/hidden.d.ts +6 -0
- package/dist/hidden.js +19 -0
- package/dist/hidden.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +944 -0
- package/dist/index.js.map +1 -0
- package/dist/number.d.ts +8 -0
- package/dist/number.js +152 -0
- package/dist/number.js.map +1 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.js +966 -0
- package/dist/register.js.map +1 -0
- package/dist/relationship.d.ts +8 -0
- package/dist/relationship.js +141 -0
- package/dist/relationship.js.map +1 -0
- package/dist/select.d.ts +10 -0
- package/dist/select.js +171 -0
- package/dist/select.js.map +1 -0
- package/dist/slug.d.ts +7 -0
- package/dist/slug.js +56 -0
- package/dist/slug.js.map +1 -0
- package/dist/text.d.ts +14 -0
- package/dist/text.js +191 -0
- package/dist/text.js.map +1 -0
- package/package.json +117 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arqel Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @arqel-dev/fields
|
|
2
|
+
|
|
3
|
+
[](../../LICENSE)
|
|
4
|
+
[](https://react.dev)
|
|
5
|
+
[](#)
|
|
6
|
+
|
|
7
|
+
Rich React input components for [Arqel](https://arqel.dev), registered into `@arqel-dev/ui`'s FieldRegistry.
|
|
8
|
+
|
|
9
|
+
## Status
|
|
10
|
+
|
|
11
|
+
🚧 **Pre-alpha** — FIELDS-JS-001 + FIELDS-JS-002 entregues (9 components básicos: TextInput, TextareaInput, EmailInput, UrlInput, PasswordInput, NumberInput, CurrencyInput, Checkbox, Toggle).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @arqel-dev/fields @arqel-dev/ui @arqel-dev/react @arqel-dev/types
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// resources/js/app.tsx
|
|
23
|
+
import '@arqel-dev/ui/styles.css';
|
|
24
|
+
import '@arqel-dev/fields/register'; // side effect: registers all built-ins
|
|
25
|
+
|
|
26
|
+
import { createArqelApp } from '@arqel-dev/react/inertia';
|
|
27
|
+
|
|
28
|
+
createArqelApp({ appName: 'Acme', pages: import.meta.glob('./Pages/**/*.tsx') });
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Subpath imports
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { TextInput, EmailInput } from '@arqel-dev/fields/text';
|
|
35
|
+
import { NumberInput, CurrencyInput } from '@arqel-dev/fields/number';
|
|
36
|
+
import { Checkbox, Toggle } from '@arqel-dev/fields/boolean';
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Links
|
|
40
|
+
|
|
41
|
+
- [Documentação](https://arqel.dev/docs/fields-js) — em construção
|
|
42
|
+
- [PLANNING](../../PLANNING/08-fase-1-mvp.md) — tickets `FIELDS-JS-*`
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# SKILL.md — @arqel-dev/fields (JS)
|
|
2
|
+
|
|
3
|
+
> Contexto canónico para AI agents.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
`@arqel-dev/fields` é o pacote de inputs ricos do lado React. Cada componente registra-se no `FieldRegistry` de `@arqel-dev/ui` (via `import '@arqel-dev/fields/register'`) e substitui os fallbacks nativos do `<FieldRenderer>`. O nome usado em `registerField('TextInput', ...)` corresponde ao retornado por `Field::component()` no PHP — a Resource servidor não precisa saber nada do React.
|
|
8
|
+
|
|
9
|
+
## Status
|
|
10
|
+
|
|
11
|
+
**Entregue (FIELDS-JS-001..006):**
|
|
12
|
+
|
|
13
|
+
- Pacote `@arqel-dev/fields` com 12 entry points subpath (`./`, `./register`, `./text`, `./number`, `./boolean`, `./select`, `./relationship`, `./date`, `./file`, `./slug`, `./color`, `./hidden`)
|
|
14
|
+
- **21 components no total** — um por field type canônico:
|
|
15
|
+
- **text/**: `TextInput`, `TextareaInput`, `EmailInput`, `UrlInput`, `PasswordInput` (toggle reveal com `aria-pressed`)
|
|
16
|
+
- **number/**: `NumberInput` (stepper buttons), `CurrencyInput` (Intl-format on blur, raw on focus)
|
|
17
|
+
- **boolean/**: `Checkbox`, `Toggle` (role=switch + iOS track/thumb)
|
|
18
|
+
- **select/**: `SelectInput` (native), `MultiSelectInput` (chips removíveis + native multiple), `RadioGroup` (role=radiogroup + inline/stacked)
|
|
19
|
+
- **relationship/**: `BelongsToInput` (async combobox via fetch + 300ms debounce ao `field.props.searchRoute`, role=combobox/listbox), `HasManyReadonly` (lista flat readonly)
|
|
20
|
+
- **date/**: `DateInput` (`type="date"` nativo + min/max), `DateTimeInput` (`type="datetime-local"` + step se `seconds`)
|
|
21
|
+
- **file/**: `FileInput` (drag-drop + section semântica, armazena `File` no form state), `ImageInput` (preview via `URL.createObjectURL`, sem crop em Phase 1)
|
|
22
|
+
- **slug/**: `SlugInput` (normaliza para `[a-z0-9-]+` no keystroke + helper `slugify`)
|
|
23
|
+
- **color/**: `ColorInput` (native picker + presets + hex text input)
|
|
24
|
+
- **hidden/**: `HiddenInput` (`type="hidden"` value carrier)
|
|
25
|
+
- Side-effect `register.ts` registra todos os 21 no FieldRegistry
|
|
26
|
+
- `getRegisteredFields()` (re-exportado de `@arqel-dev/ui/form`) retorna nomes registrados ordenados
|
|
27
|
+
- 23 testes Vitest passando — registry roundtrip, behaviour + a11y de cada componente
|
|
28
|
+
- Estilo via CSS vars de `@arqel-dev/ui` (sem hardcode); `aria-invalid` quando há erros
|
|
29
|
+
|
|
30
|
+
**Por chegar (Phase 2):**
|
|
31
|
+
|
|
32
|
+
- Image crop (`react-image-crop`)
|
|
33
|
+
- Date-picker custom (`react-day-picker`)
|
|
34
|
+
- Combobox searchable de Base UI para `SelectInput`
|
|
35
|
+
- `SlugInput` auto-derivado de outro field (depende de FormContext exposed pelo `useArqelForm`)
|
|
36
|
+
|
|
37
|
+
## Creating a custom field
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// resources/js/fields/MoneyInput.tsx
|
|
41
|
+
import type { FieldRendererProps } from '@arqel-dev/ui/form';
|
|
42
|
+
|
|
43
|
+
export function MoneyInput({ field, value, onChange, errors, inputId }: FieldRendererProps) {
|
|
44
|
+
const hasError = errors !== undefined && errors.length > 0;
|
|
45
|
+
return (
|
|
46
|
+
<input
|
|
47
|
+
id={inputId}
|
|
48
|
+
type="text"
|
|
49
|
+
inputMode="decimal"
|
|
50
|
+
className="…"
|
|
51
|
+
value={typeof value === 'string' ? value : ''}
|
|
52
|
+
aria-invalid={hasError || undefined}
|
|
53
|
+
onChange={(e) => onChange(e.target.value)}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// resources/js/app.tsx
|
|
61
|
+
import '@arqel-dev/ui/styles.css';
|
|
62
|
+
import '@arqel-dev/fields/register';
|
|
63
|
+
import { registerField } from '@arqel-dev/ui/form';
|
|
64
|
+
import { MoneyInput } from './fields/MoneyInput';
|
|
65
|
+
|
|
66
|
+
registerField('MoneyInput', MoneyInput); // depois do register.ts default
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```php
|
|
70
|
+
// app/Arqel/Fields/MoneyField.php
|
|
71
|
+
final class MoneyField extends Field
|
|
72
|
+
{
|
|
73
|
+
protected string $type = 'money';
|
|
74
|
+
protected string $component = 'MoneyInput';
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Key Contracts
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// resources/js/app.tsx
|
|
82
|
+
import '@arqel-dev/ui/styles.css';
|
|
83
|
+
import '@arqel-dev/fields/register'; // side effect: registra inputs ricos
|
|
84
|
+
import { createArqelApp } from '@arqel-dev/react/inertia';
|
|
85
|
+
|
|
86
|
+
createArqelApp({
|
|
87
|
+
appName: 'Acme Admin',
|
|
88
|
+
pages: import.meta.glob('./Pages/**/*.tsx'),
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
A partir desse import, `<FieldRenderer>` resolve `field.component === 'TextInput'` para o componente rico. Sem o import, cai no fallback nativo de `@arqel-dev/ui/form` (`nativeFields.tsx`).
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// Override custom: registre depois do side-effect import
|
|
96
|
+
import { registerField } from '@arqel-dev/ui/form';
|
|
97
|
+
import { MyFancyText } from './MyFancyText';
|
|
98
|
+
|
|
99
|
+
registerField('TextInput', MyFancyText);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Conventions
|
|
103
|
+
|
|
104
|
+
- **Nome do componente** segue o `Field::component()` retornado pelo PHP
|
|
105
|
+
- **Props canônicas** vêm de `FieldRendererProps` (re-exportado de `@arqel-dev/ui/form`): `field`, `value`, `onChange`, `errors`, `disabled`, `inputId`, `describedBy`
|
|
106
|
+
- **Estilos** sempre via `@arqel-dev/ui/utils#cn` + CSS vars (`--color-arqel-*`) — nunca hardcode
|
|
107
|
+
- **A11y**: `aria-invalid` quando `errors.length > 0`, `aria-describedby` propagado, labels associados via `inputId` (gerenciado pelo `<FieldRenderer>`)
|
|
108
|
+
- **Side-effect entry**: `register.ts` é o único arquivo com `sideEffects: true` no `package.json`
|
|
109
|
+
|
|
110
|
+
## Anti-patterns
|
|
111
|
+
|
|
112
|
+
- ❌ **Importar `register.ts` mais de uma vez** — registra 2x o mesmo componente; se você precisa override, chame `registerField` direto
|
|
113
|
+
- ❌ **Criar wrapper `<label>` interno** — `<FieldRenderer>` já faz isso; o componente recebe `inputId` para `<input id={inputId}>`
|
|
114
|
+
- ❌ **Hardcode de cor** — usa CSS vars
|
|
115
|
+
- ❌ **Importar de `@arqel-dev/fields/text/TextInput.js`** — usa subpaths declarados (`@arqel-dev/fields/text`)
|
|
116
|
+
- ❌ **Render label dentro do componente** — `<FieldRenderer>` é o dono do label
|
|
117
|
+
|
|
118
|
+
## Related
|
|
119
|
+
|
|
120
|
+
- Tickets: [`PLANNING/08-fase-1-mvp.md`](../../PLANNING/08-fase-1-mvp.md) §FIELDS-JS-001..006
|
|
121
|
+
- API: [`PLANNING/06-api-react.md`](../../PLANNING/06-api-react.md) §10
|
|
122
|
+
- Source: [`packages-js/fields-js/src/`](src/)
|
|
123
|
+
- Tests: [`packages-js/fields-js/tests/`](tests/)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { FieldRendererProps } from '@arqel-dev/ui/form';
|
|
3
|
+
|
|
4
|
+
declare function Checkbox({ field, value, onChange, errors, disabled, inputId, describedBy, }: FieldRendererProps): react_jsx_runtime.JSX.Element;
|
|
5
|
+
|
|
6
|
+
declare function Toggle({ field, value, onChange, errors, disabled, inputId, describedBy, }: FieldRendererProps): react_jsx_runtime.JSX.Element;
|
|
7
|
+
|
|
8
|
+
export { Checkbox, Toggle };
|
package/dist/boolean.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { cn } from '@arqel-dev/ui/utils';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/shared/styles.ts
|
|
5
|
+
cn(
|
|
6
|
+
"h-9 w-full rounded-sm border border-[var(--input)]",
|
|
7
|
+
"bg-background px-3 text-sm text-foreground",
|
|
8
|
+
"placeholder:text-muted-foreground",
|
|
9
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
10
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
"aria-invalid:border-destructive"
|
|
12
|
+
);
|
|
13
|
+
var checkboxClasses = cn(
|
|
14
|
+
"h-4 w-4 rounded border-[var(--input)]",
|
|
15
|
+
"text-primary",
|
|
16
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
17
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
18
|
+
);
|
|
19
|
+
function Checkbox({
|
|
20
|
+
field,
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
errors,
|
|
24
|
+
disabled,
|
|
25
|
+
inputId,
|
|
26
|
+
describedBy
|
|
27
|
+
}) {
|
|
28
|
+
const f = field;
|
|
29
|
+
const hasError = errors !== void 0 && errors.length > 0;
|
|
30
|
+
return /* @__PURE__ */ jsx(
|
|
31
|
+
"input",
|
|
32
|
+
{
|
|
33
|
+
id: inputId,
|
|
34
|
+
type: "checkbox",
|
|
35
|
+
className: checkboxClasses,
|
|
36
|
+
checked: value === true,
|
|
37
|
+
disabled: disabled || f.disabled || f.readonly,
|
|
38
|
+
"aria-invalid": hasError || void 0,
|
|
39
|
+
"aria-describedby": describedBy,
|
|
40
|
+
onChange: (e) => onChange(e.target.checked)
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
function Toggle({
|
|
45
|
+
field,
|
|
46
|
+
value,
|
|
47
|
+
onChange,
|
|
48
|
+
errors,
|
|
49
|
+
disabled,
|
|
50
|
+
inputId,
|
|
51
|
+
describedBy
|
|
52
|
+
}) {
|
|
53
|
+
const f = field;
|
|
54
|
+
const checked = value === true;
|
|
55
|
+
const hasError = errors !== void 0 && errors.length > 0;
|
|
56
|
+
const isDisabled = disabled || f.disabled || f.readonly;
|
|
57
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
58
|
+
/* @__PURE__ */ jsx(
|
|
59
|
+
"button",
|
|
60
|
+
{
|
|
61
|
+
id: inputId,
|
|
62
|
+
type: "button",
|
|
63
|
+
role: "switch",
|
|
64
|
+
"aria-checked": checked,
|
|
65
|
+
"aria-invalid": hasError || void 0,
|
|
66
|
+
"aria-describedby": describedBy,
|
|
67
|
+
disabled: isDisabled,
|
|
68
|
+
onClick: () => onChange(!checked),
|
|
69
|
+
className: cn(
|
|
70
|
+
"relative inline-flex h-6 w-11 items-center rounded-full transition-colors",
|
|
71
|
+
checked ? "bg-primary" : "bg-muted",
|
|
72
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
73
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
74
|
+
),
|
|
75
|
+
children: /* @__PURE__ */ jsx(
|
|
76
|
+
"span",
|
|
77
|
+
{
|
|
78
|
+
"aria-hidden": "true",
|
|
79
|
+
className: cn(
|
|
80
|
+
"inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform",
|
|
81
|
+
checked ? "translate-x-5" : "translate-x-0.5"
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
(f.props.onLabel || f.props.offLabel) && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: checked ? f.props.onLabel : f.props.offLabel })
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { Checkbox, Toggle };
|
|
92
|
+
//# sourceMappingURL=boolean.js.map
|
|
93
|
+
//# sourceMappingURL=boolean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/styles.ts","../src/boolean/Checkbox.tsx","../src/boolean/Toggle.tsx"],"names":["jsx","cn"],"mappings":";;;;AAO4B,EAAA;AAAA,EAC1B,oDAAA;AAAA,EACA,4CAAA;AAAA,EACA,mCAAA;AAAA,EACA,yEAAA;AAAA,EACA,iDAAA;AAAA,EACA;AACF;AAEO,IAAM,eAAA,GAAkB,EAAA;AAAA,EAC7B,uCAAA;AAAA,EACA,cAAA;AAAA,EACA,yEAAA;AAAA,EACA;AACF,CAAA;ACjBO,SAAS,QAAA,CAAS;AAAA,EACvB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,uBACE,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAI,OAAA;AAAA,MACJ,IAAA,EAAK,UAAA;AAAA,MACL,SAAA,EAAW,eAAA;AAAA,MACX,SAAS,KAAA,KAAU,IAAA;AAAA,MACnB,QAAA,EAAU,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAAA,MACtC,gBAAc,QAAA,IAAY,MAAA;AAAA,MAC1B,kBAAA,EAAkB,WAAA;AAAA,MAClB,UAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,OAAO,OAAO;AAAA;AAAA,GAC5C;AAEJ;AChBO,SAAS,MAAA,CAAO;AAAA,EACrB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,UAAU,KAAA,KAAU,IAAA;AAC1B,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,MAAM,UAAA,GAAa,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAE/C,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,oBAAAA,GAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,OAAA;AAAA,QACJ,IAAA,EAAK,QAAA;AAAA,QACL,IAAA,EAAK,QAAA;AAAA,QACL,cAAA,EAAc,OAAA;AAAA,QACd,gBAAc,QAAA,IAAY,MAAA;AAAA,QAC1B,kBAAA,EAAkB,WAAA;AAAA,QAClB,QAAA,EAAU,UAAA;AAAA,QACV,OAAA,EAAS,MAAM,QAAA,CAAS,CAAC,OAAO,CAAA;AAAA,QAChC,SAAA,EAAWC,EAAAA;AAAA,UACT,2EAAA;AAAA,UACA,UAAU,YAAA,GAAe,UAAA;AAAA,UACzB,yEAAA;AAAA,UACA;AAAA,SACF;AAAA,QAEA,QAAA,kBAAAD,GAAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,aAAA,EAAY,MAAA;AAAA,YACZ,SAAA,EAAWC,EAAAA;AAAA,cACT,kFAAA;AAAA,cACA,UAAU,eAAA,GAAkB;AAAA;AAC9B;AAAA;AACF;AAAA,KACF;AAAA,IAAA,CACE,EAAE,KAAA,CAAM,OAAA,IAAW,CAAA,CAAE,KAAA,CAAM,6BAC3BD,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iCACb,QAAA,EAAA,OAAA,GAAU,CAAA,CAAE,MAAM,OAAA,GAAU,CAAA,CAAE,MAAM,QAAA,EACvC;AAAA,GAAA,EAEJ,CAAA;AAEJ","file":"boolean.js","sourcesContent":["/**\n * Shared input styling — keeps the visual contract consistent across\n * every Field component without forcing a wrapper component.\n */\n\nimport { cn } from '@arqel-dev/ui/utils';\n\nexport const inputClasses = cn(\n 'h-9 w-full rounded-sm border border-[var(--input)]',\n 'bg-background px-3 text-sm text-foreground',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-invalid:border-destructive',\n);\n\nexport const checkboxClasses = cn(\n 'h-4 w-4 rounded border-[var(--input)]',\n 'text-primary',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n);\n","import type { BooleanFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { checkboxClasses } from '../shared/styles.js';\n\nexport function Checkbox({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as BooleanFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n return (\n <input\n id={inputId}\n type=\"checkbox\"\n className={checkboxClasses}\n checked={value === true}\n disabled={disabled || f.disabled || f.readonly}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.checked)}\n />\n );\n}\n","/**\n * `<Toggle>` — switch-style boolean control with optional on/off labels.\n *\n * Renders as a `role=\"switch\"` button so screen readers announce it\n * correctly; styling is the canonical iOS-style track/thumb.\n */\n\nimport type { ToggleFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { cn } from '@arqel-dev/ui/utils';\n\nexport function Toggle({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as ToggleFieldSchema;\n const checked = value === true;\n const hasError = errors !== undefined && errors.length > 0;\n const isDisabled = disabled || f.disabled || f.readonly;\n\n return (\n <div className=\"flex items-center gap-3\">\n <button\n id={inputId}\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n disabled={isDisabled}\n onClick={() => onChange(!checked)}\n className={cn(\n 'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',\n checked ? 'bg-primary' : 'bg-muted',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n )}\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n 'inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform',\n checked ? 'translate-x-5' : 'translate-x-0.5',\n )}\n />\n </button>\n {(f.props.onLabel || f.props.offLabel) && (\n <span className=\"text-sm text-muted-foreground\">\n {checked ? f.props.onLabel : f.props.offLabel}\n </span>\n )}\n </div>\n );\n}\n"]}
|
package/dist/color.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { FieldRendererProps } from '@arqel-dev/ui/form';
|
|
3
|
+
|
|
4
|
+
declare function ColorInput({ field, value, onChange, errors, disabled, inputId, describedBy, }: FieldRendererProps): react_jsx_runtime.JSX.Element;
|
|
5
|
+
|
|
6
|
+
export { ColorInput };
|
package/dist/color.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { cn } from '@arqel-dev/ui/utils';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/color/ColorInput.tsx
|
|
5
|
+
function ColorInput({
|
|
6
|
+
field,
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
errors,
|
|
10
|
+
disabled,
|
|
11
|
+
inputId,
|
|
12
|
+
describedBy
|
|
13
|
+
}) {
|
|
14
|
+
const f = field;
|
|
15
|
+
const hasError = errors !== void 0 && errors.length > 0;
|
|
16
|
+
const isDisabled = disabled || f.disabled || f.readonly;
|
|
17
|
+
const current = typeof value === "string" && value.length > 0 ? value : "#000000";
|
|
18
|
+
const presets = f.props.presets ?? [];
|
|
19
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
20
|
+
/* @__PURE__ */ jsx(
|
|
21
|
+
"input",
|
|
22
|
+
{
|
|
23
|
+
id: inputId,
|
|
24
|
+
type: "color",
|
|
25
|
+
className: cn(
|
|
26
|
+
"h-9 w-12 cursor-pointer rounded-sm border border-[var(--input)]",
|
|
27
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
28
|
+
),
|
|
29
|
+
value: current,
|
|
30
|
+
disabled: isDisabled,
|
|
31
|
+
"aria-invalid": hasError || void 0,
|
|
32
|
+
"aria-describedby": describedBy,
|
|
33
|
+
onChange: (e) => onChange(e.target.value)
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
/* @__PURE__ */ jsx(
|
|
37
|
+
"input",
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
className: "h-9 w-28 rounded-sm border border-[var(--input)] bg-background px-2 font-mono text-xs",
|
|
41
|
+
value: current,
|
|
42
|
+
disabled: isDisabled,
|
|
43
|
+
onChange: (e) => onChange(e.target.value)
|
|
44
|
+
}
|
|
45
|
+
),
|
|
46
|
+
presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: presets.map((preset) => /* @__PURE__ */ jsx(
|
|
47
|
+
"button",
|
|
48
|
+
{
|
|
49
|
+
type: "button",
|
|
50
|
+
"aria-label": `Preset ${preset}`,
|
|
51
|
+
disabled: isDisabled,
|
|
52
|
+
onClick: () => onChange(preset),
|
|
53
|
+
className: "h-6 w-6 rounded-full border border-border disabled:cursor-not-allowed disabled:opacity-50",
|
|
54
|
+
style: { backgroundColor: preset }
|
|
55
|
+
},
|
|
56
|
+
preset
|
|
57
|
+
)) })
|
|
58
|
+
] });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { ColorInput };
|
|
62
|
+
//# sourceMappingURL=color.js.map
|
|
63
|
+
//# sourceMappingURL=color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/color/ColorInput.tsx"],"names":[],"mappings":";;;;AAYO,SAAS,UAAA,CAAW;AAAA,EACzB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,MAAM,UAAA,GAAa,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAC/C,EAAA,MAAM,UAAU,OAAO,KAAA,KAAU,YAAY,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,GAAQ,SAAA;AACxE,EAAA,MAAM,OAAA,GAAU,CAAA,CAAE,KAAA,CAAM,OAAA,IAAW,EAAC;AAEpC,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mCAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,OAAA;AAAA,QACJ,IAAA,EAAK,OAAA;AAAA,QACL,SAAA,EAAW,EAAA;AAAA,UACT,iEAAA;AAAA,UACA;AAAA,SACF;AAAA,QACA,KAAA,EAAO,OAAA;AAAA,QACP,QAAA,EAAU,UAAA;AAAA,QACV,gBAAc,QAAA,IAAY,MAAA;AAAA,QAC1B,kBAAA,EAAkB,WAAA;AAAA,QAClB,UAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA,KAC1C;AAAA,oBACA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,MAAA;AAAA,QACL,SAAA,EAAU,uFAAA;AAAA,QACV,KAAA,EAAO,OAAA;AAAA,QACP,QAAA,EAAU,UAAA;AAAA,QACV,UAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA,KAC1C;AAAA,IACC,OAAA,CAAQ,MAAA,GAAS,CAAA,oBAChB,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,sBAAA,EACZ,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,qBACZ,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QAEC,IAAA,EAAK,QAAA;AAAA,QACL,YAAA,EAAY,UAAU,MAAM,CAAA,CAAA;AAAA,QAC5B,QAAA,EAAU,UAAA;AAAA,QACV,OAAA,EAAS,MAAM,QAAA,CAAS,MAAM,CAAA;AAAA,QAC9B,SAAA,EAAU,2FAAA;AAAA,QACV,KAAA,EAAO,EAAE,eAAA,EAAiB,MAAA;AAAO,OAAA;AAAA,MAN5B;AAAA,KAQR,CAAA,EACH;AAAA,GAAA,EAEJ,CAAA;AAEJ","file":"color.js","sourcesContent":["/**\n * `<ColorInput>` — native color picker plus optional preset swatches.\n *\n * `field.props.presets: string[]` (hex strings) renders a strip of\n * clickable swatches above the picker. The native `<input type=\"color\">`\n * is a/11y-friendly out of the box.\n */\n\nimport type { ColorFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { cn } from '@arqel-dev/ui/utils';\n\nexport function ColorInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as ColorFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n const isDisabled = disabled || f.disabled || f.readonly;\n const current = typeof value === 'string' && value.length > 0 ? value : '#000000';\n const presets = f.props.presets ?? [];\n\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n <input\n id={inputId}\n type=\"color\"\n className={cn(\n 'h-9 w-12 cursor-pointer rounded-sm border border-[var(--input)]',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n )}\n value={current}\n disabled={isDisabled}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.value)}\n />\n <input\n type=\"text\"\n className=\"h-9 w-28 rounded-sm border border-[var(--input)] bg-background px-2 font-mono text-xs\"\n value={current}\n disabled={isDisabled}\n onChange={(e) => onChange(e.target.value)}\n />\n {presets.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {presets.map((preset) => (\n <button\n key={preset}\n type=\"button\"\n aria-label={`Preset ${preset}`}\n disabled={isDisabled}\n onClick={() => onChange(preset)}\n className=\"h-6 w-6 rounded-full border border-border disabled:cursor-not-allowed disabled:opacity-50\"\n style={{ backgroundColor: preset }}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"]}
|
package/dist/date.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { FieldRendererProps } from '@arqel-dev/ui/form';
|
|
3
|
+
|
|
4
|
+
declare function DateInput({ field, value, onChange, errors, disabled, inputId, describedBy, }: FieldRendererProps): react_jsx_runtime.JSX.Element;
|
|
5
|
+
|
|
6
|
+
declare function DateTimeInput({ field, value, onChange, errors, disabled, inputId, describedBy, }: FieldRendererProps): react_jsx_runtime.JSX.Element;
|
|
7
|
+
|
|
8
|
+
export { DateInput, DateTimeInput };
|
package/dist/date.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { cn } from '@arqel-dev/ui/utils';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/shared/styles.ts
|
|
5
|
+
var inputClasses = cn(
|
|
6
|
+
"h-9 w-full rounded-sm border border-[var(--input)]",
|
|
7
|
+
"bg-background px-3 text-sm text-foreground",
|
|
8
|
+
"placeholder:text-muted-foreground",
|
|
9
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
10
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
"aria-invalid:border-destructive"
|
|
12
|
+
);
|
|
13
|
+
cn(
|
|
14
|
+
"h-4 w-4 rounded border-[var(--input)]",
|
|
15
|
+
"text-primary",
|
|
16
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
17
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
18
|
+
);
|
|
19
|
+
function DateInput({
|
|
20
|
+
field,
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
errors,
|
|
24
|
+
disabled,
|
|
25
|
+
inputId,
|
|
26
|
+
describedBy
|
|
27
|
+
}) {
|
|
28
|
+
const f = field;
|
|
29
|
+
const hasError = errors !== void 0 && errors.length > 0;
|
|
30
|
+
return /* @__PURE__ */ jsx(
|
|
31
|
+
"input",
|
|
32
|
+
{
|
|
33
|
+
id: inputId,
|
|
34
|
+
type: "date",
|
|
35
|
+
className: inputClasses,
|
|
36
|
+
value: typeof value === "string" ? value : "",
|
|
37
|
+
disabled: disabled || f.disabled || f.readonly,
|
|
38
|
+
readOnly: f.readonly === true,
|
|
39
|
+
required: f.required === true,
|
|
40
|
+
min: f.props.minDate,
|
|
41
|
+
max: f.props.maxDate,
|
|
42
|
+
"aria-invalid": hasError || void 0,
|
|
43
|
+
"aria-describedby": describedBy,
|
|
44
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
function DateTimeInput({
|
|
49
|
+
field,
|
|
50
|
+
value,
|
|
51
|
+
onChange,
|
|
52
|
+
errors,
|
|
53
|
+
disabled,
|
|
54
|
+
inputId,
|
|
55
|
+
describedBy
|
|
56
|
+
}) {
|
|
57
|
+
const f = field;
|
|
58
|
+
const hasError = errors !== void 0 && errors.length > 0;
|
|
59
|
+
return /* @__PURE__ */ jsx(
|
|
60
|
+
"input",
|
|
61
|
+
{
|
|
62
|
+
id: inputId,
|
|
63
|
+
type: "datetime-local",
|
|
64
|
+
className: inputClasses,
|
|
65
|
+
value: typeof value === "string" ? value : "",
|
|
66
|
+
disabled: disabled || f.disabled || f.readonly,
|
|
67
|
+
readOnly: f.readonly === true,
|
|
68
|
+
required: f.required === true,
|
|
69
|
+
min: f.props.minDate,
|
|
70
|
+
max: f.props.maxDate,
|
|
71
|
+
step: f.props.seconds ? 1 : void 0,
|
|
72
|
+
"aria-invalid": hasError || void 0,
|
|
73
|
+
"aria-describedby": describedBy,
|
|
74
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { DateInput, DateTimeInput };
|
|
80
|
+
//# sourceMappingURL=date.js.map
|
|
81
|
+
//# sourceMappingURL=date.js.map
|
package/dist/date.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/styles.ts","../src/date/DateInput.tsx","../src/date/DateTimeInput.tsx"],"names":["jsx"],"mappings":";;;;AAOO,IAAM,YAAA,GAAe,EAAA;AAAA,EAC1B,oDAAA;AAAA,EACA,4CAAA;AAAA,EACA,mCAAA;AAAA,EACA,yEAAA;AAAA,EACA,iDAAA;AAAA,EACA;AACF,CAAA;AAE+B,EAAA;AAAA,EAC7B,uCAAA;AAAA,EACA,cAAA;AAAA,EACA,yEAAA;AAAA,EACA;AACF;ACRO,SAAS,SAAA,CAAU;AAAA,EACxB,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,uBACE,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAI,OAAA;AAAA,MACJ,IAAA,EAAK,MAAA;AAAA,MACL,SAAA,EAAW,YAAA;AAAA,MACX,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,EAAA;AAAA,MAC3C,QAAA,EAAU,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAAA,MACtC,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,gBAAc,QAAA,IAAY,MAAA;AAAA,MAC1B,kBAAA,EAAkB,WAAA;AAAA,MAClB,QAAA,EAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,MAAA,CAAO,KAAA,KAAU,EAAA,GAAK,IAAA,GAAO,CAAA,CAAE,MAAA,CAAO,KAAK;AAAA;AAAA,GACzE;AAEJ;ACpCO,SAAS,aAAA,CAAc;AAAA,EAC5B,KAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,MAAM,QAAA,GAAW,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,MAAA,GAAS,CAAA;AACzD,EAAA,uBACEA,GAAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAI,OAAA;AAAA,MACJ,IAAA,EAAK,gBAAA;AAAA,MACL,SAAA,EAAW,YAAA;AAAA,MACX,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,EAAA;AAAA,MAC3C,QAAA,EAAU,QAAA,IAAY,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAA;AAAA,MACtC,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,QAAA,EAAU,EAAE,QAAA,KAAa,IAAA;AAAA,MACzB,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,GAAA,EAAK,EAAE,KAAA,CAAM,OAAA;AAAA,MACb,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,OAAA,GAAU,CAAA,GAAI,MAAA;AAAA,MAC5B,gBAAc,QAAA,IAAY,MAAA;AAAA,MAC1B,kBAAA,EAAkB,WAAA;AAAA,MAClB,QAAA,EAAU,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA,CAAE,MAAA,CAAO,KAAA,KAAU,EAAA,GAAK,IAAA,GAAO,CAAA,CAAE,MAAA,CAAO,KAAK;AAAA;AAAA,GACzE;AAEJ","file":"date.js","sourcesContent":["/**\n * Shared input styling — keeps the visual contract consistent across\n * every Field component without forcing a wrapper component.\n */\n\nimport { cn } from '@arqel-dev/ui/utils';\n\nexport const inputClasses = cn(\n 'h-9 w-full rounded-sm border border-[var(--input)]',\n 'bg-background px-3 text-sm text-foreground',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-invalid:border-destructive',\n);\n\nexport const checkboxClasses = cn(\n 'h-4 w-4 rounded border-[var(--input)]',\n 'text-primary',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n);\n","/**\n * `<DateInput>` — native `<input type=\"date\">`.\n *\n * Phase 1 leans on the browser's built-in date picker for footprint\n * reasons. A `react-day-picker`-based custom picker lands in Phase 2\n * when we need locale-specific formatting + min/max enforcement\n * beyond what the native control provides.\n */\n\nimport type { DateFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { inputClasses } from '../shared/styles.js';\n\nexport function DateInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as DateFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n return (\n <input\n id={inputId}\n type=\"date\"\n className={inputClasses}\n value={typeof value === 'string' ? value : ''}\n disabled={disabled || f.disabled || f.readonly}\n readOnly={f.readonly === true}\n required={f.required === true}\n min={f.props.minDate}\n max={f.props.maxDate}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.value === '' ? null : e.target.value)}\n />\n );\n}\n","import type { DateTimeFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { inputClasses } from '../shared/styles.js';\n\nexport function DateTimeInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as DateTimeFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n return (\n <input\n id={inputId}\n type=\"datetime-local\"\n className={inputClasses}\n value={typeof value === 'string' ? value : ''}\n disabled={disabled || f.disabled || f.readonly}\n readOnly={f.readonly === true}\n required={f.required === true}\n min={f.props.minDate}\n max={f.props.maxDate}\n step={f.props.seconds ? 1 : undefined}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => onChange(e.target.value === '' ? null : e.target.value)}\n />\n );\n}\n"]}
|
package/dist/file.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { FieldRendererProps } from '@arqel-dev/ui/form';
|
|
3
|
+
|
|
4
|
+
declare function FileInput({ field, value, onChange, errors, disabled, inputId, describedBy, }: FieldRendererProps): react_jsx_runtime.JSX.Element;
|
|
5
|
+
|
|
6
|
+
declare function ImageInput({ field, value, onChange, errors, disabled, inputId, describedBy, }: FieldRendererProps): react_jsx_runtime.JSX.Element;
|
|
7
|
+
|
|
8
|
+
export { FileInput, ImageInput };
|
package/dist/file.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { cn } from '@arqel-dev/ui/utils';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/file/FileInput.tsx
|
|
6
|
+
function FileInput({
|
|
7
|
+
field,
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
errors,
|
|
11
|
+
disabled,
|
|
12
|
+
inputId,
|
|
13
|
+
describedBy
|
|
14
|
+
}) {
|
|
15
|
+
const f = field;
|
|
16
|
+
const hasError = errors !== void 0 && errors.length > 0;
|
|
17
|
+
const isDisabled = disabled || f.disabled || f.readonly;
|
|
18
|
+
const [dragOver, setDragOver] = useState(false);
|
|
19
|
+
const file = value instanceof File ? value : null;
|
|
20
|
+
const filename = file?.name ?? (typeof value === "string" ? value : null);
|
|
21
|
+
const handleFiles = (files) => {
|
|
22
|
+
if (!files || files.length === 0) return;
|
|
23
|
+
onChange(files[0]);
|
|
24
|
+
};
|
|
25
|
+
return /* @__PURE__ */ jsxs(
|
|
26
|
+
"section",
|
|
27
|
+
{
|
|
28
|
+
"aria-label": "File upload",
|
|
29
|
+
onDragOver: (e) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
if (!isDisabled) setDragOver(true);
|
|
32
|
+
},
|
|
33
|
+
onDragLeave: () => setDragOver(false),
|
|
34
|
+
onDrop: (e) => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
setDragOver(false);
|
|
37
|
+
if (!isDisabled) handleFiles(e.dataTransfer.files);
|
|
38
|
+
},
|
|
39
|
+
className: cn(
|
|
40
|
+
"flex flex-col items-center justify-center gap-2 rounded-sm border-2 border-dashed px-4 py-6 text-sm",
|
|
41
|
+
dragOver ? "border-primary bg-muted" : "border-border",
|
|
42
|
+
hasError && "border-destructive",
|
|
43
|
+
isDisabled && "opacity-50"
|
|
44
|
+
),
|
|
45
|
+
children: [
|
|
46
|
+
filename ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: filename }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Drag a file here or click to browse" }),
|
|
47
|
+
/* @__PURE__ */ jsxs("label", { className: "cursor-pointer text-xs text-primary hover:underline", children: [
|
|
48
|
+
filename ? "Choose another file" : "Browse",
|
|
49
|
+
/* @__PURE__ */ jsx(
|
|
50
|
+
"input",
|
|
51
|
+
{
|
|
52
|
+
id: inputId,
|
|
53
|
+
type: "file",
|
|
54
|
+
className: "sr-only",
|
|
55
|
+
disabled: isDisabled,
|
|
56
|
+
"aria-invalid": hasError || void 0,
|
|
57
|
+
"aria-describedby": describedBy,
|
|
58
|
+
onChange: (e) => handleFiles(e.target.files)
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
] })
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function ImageInput({
|
|
67
|
+
field,
|
|
68
|
+
value,
|
|
69
|
+
onChange,
|
|
70
|
+
errors,
|
|
71
|
+
disabled,
|
|
72
|
+
inputId,
|
|
73
|
+
describedBy
|
|
74
|
+
}) {
|
|
75
|
+
const f = field;
|
|
76
|
+
const hasError = errors !== void 0 && errors.length > 0;
|
|
77
|
+
const isDisabled = disabled || f.disabled || f.readonly;
|
|
78
|
+
const [previewUrl, setPreviewUrl] = useState(null);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (value instanceof File) {
|
|
81
|
+
const url = URL.createObjectURL(value);
|
|
82
|
+
setPreviewUrl(url);
|
|
83
|
+
return () => URL.revokeObjectURL(url);
|
|
84
|
+
}
|
|
85
|
+
if (typeof value === "string" && value.length > 0) {
|
|
86
|
+
setPreviewUrl(value);
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
setPreviewUrl(null);
|
|
90
|
+
return void 0;
|
|
91
|
+
}, [value]);
|
|
92
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
93
|
+
previewUrl && /* @__PURE__ */ jsx(
|
|
94
|
+
"img",
|
|
95
|
+
{
|
|
96
|
+
src: previewUrl,
|
|
97
|
+
alt: "Preview",
|
|
98
|
+
className: cn("max-h-40 w-auto rounded-sm border border-border")
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
/* @__PURE__ */ jsxs(
|
|
102
|
+
"label",
|
|
103
|
+
{
|
|
104
|
+
className: cn(
|
|
105
|
+
"inline-flex w-fit cursor-pointer items-center gap-2 rounded-sm border border-border px-3 py-1.5 text-sm",
|
|
106
|
+
"hover:bg-muted",
|
|
107
|
+
isDisabled && "cursor-not-allowed opacity-50"
|
|
108
|
+
),
|
|
109
|
+
children: [
|
|
110
|
+
previewUrl ? "Replace image" : "Choose image",
|
|
111
|
+
/* @__PURE__ */ jsx(
|
|
112
|
+
"input",
|
|
113
|
+
{
|
|
114
|
+
id: inputId,
|
|
115
|
+
type: "file",
|
|
116
|
+
accept: "image/*",
|
|
117
|
+
className: "sr-only",
|
|
118
|
+
disabled: isDisabled,
|
|
119
|
+
"aria-invalid": hasError || void 0,
|
|
120
|
+
"aria-describedby": describedBy,
|
|
121
|
+
onChange: (e) => {
|
|
122
|
+
const file = e.target.files?.[0];
|
|
123
|
+
if (file) onChange(file);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
] });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { FileInput, ImageInput };
|
|
134
|
+
//# sourceMappingURL=file.js.map
|
|
135
|
+
//# sourceMappingURL=file.js.map
|
package/dist/file.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/file/FileInput.tsx","../src/file/ImageInput.tsx"],"names":["useState","jsxs","jsx","cn"],"mappings":"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","file":"file.js","sourcesContent":["/**\n * `<FileInput>` — drag-drop file picker.\n *\n * Phase 1 stores the selected `File` object directly in the form\n * state; the actual upload happens server-side via Inertia's\n * multipart-aware form submission. Progress events are exposed\n * via `useArqelForm`'s `progress` field (Inertia native).\n */\n\nimport type { FileFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { cn } from '@arqel-dev/ui/utils';\nimport { useState } from 'react';\n\nexport function FileInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as FileFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n const isDisabled = disabled || f.disabled || f.readonly;\n const [dragOver, setDragOver] = useState(false);\n\n const file = value instanceof File ? value : null;\n const filename = file?.name ?? (typeof value === 'string' ? value : null);\n\n const handleFiles = (files: FileList | null) => {\n if (!files || files.length === 0) return;\n onChange(files[0]);\n };\n\n return (\n <section\n aria-label=\"File upload\"\n onDragOver={(e) => {\n e.preventDefault();\n if (!isDisabled) setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragOver(false);\n if (!isDisabled) handleFiles(e.dataTransfer.files);\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-2 rounded-sm border-2 border-dashed px-4 py-6 text-sm',\n dragOver ? 'border-primary bg-muted' : 'border-border',\n hasError && 'border-destructive',\n isDisabled && 'opacity-50',\n )}\n >\n {filename ? (\n <span className=\"font-medium\">{filename}</span>\n ) : (\n <span className=\"text-muted-foreground\">Drag a file here or click to browse</span>\n )}\n <label className=\"cursor-pointer text-xs text-primary hover:underline\">\n {filename ? 'Choose another file' : 'Browse'}\n <input\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n disabled={isDisabled}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => handleFiles(e.target.files)}\n />\n </label>\n </section>\n );\n}\n","/**\n * `<ImageInput>` — image picker with preview.\n *\n * Phase 1 ships preview-only (URL or local `URL.createObjectURL`).\n * Crop / aspect-ratio editor lands in Phase 2 with `react-image-crop`.\n */\n\nimport type { ImageFieldSchema } from '@arqel-dev/types/fields';\nimport type { FieldRendererProps } from '@arqel-dev/ui/form';\nimport { cn } from '@arqel-dev/ui/utils';\nimport { useEffect, useState } from 'react';\n\nexport function ImageInput({\n field,\n value,\n onChange,\n errors,\n disabled,\n inputId,\n describedBy,\n}: FieldRendererProps) {\n const f = field as ImageFieldSchema;\n const hasError = errors !== undefined && errors.length > 0;\n const isDisabled = disabled || f.disabled || f.readonly;\n\n const [previewUrl, setPreviewUrl] = useState<string | null>(null);\n\n useEffect(() => {\n if (value instanceof File) {\n const url = URL.createObjectURL(value);\n setPreviewUrl(url);\n return () => URL.revokeObjectURL(url);\n }\n if (typeof value === 'string' && value.length > 0) {\n setPreviewUrl(value);\n return undefined;\n }\n setPreviewUrl(null);\n return undefined;\n }, [value]);\n\n return (\n <div className=\"flex flex-col gap-2\">\n {previewUrl && (\n <img\n src={previewUrl}\n alt=\"Preview\"\n className={cn('max-h-40 w-auto rounded-sm border border-border')}\n />\n )}\n <label\n className={cn(\n 'inline-flex w-fit cursor-pointer items-center gap-2 rounded-sm border border-border px-3 py-1.5 text-sm',\n 'hover:bg-muted',\n isDisabled && 'cursor-not-allowed opacity-50',\n )}\n >\n {previewUrl ? 'Replace image' : 'Choose image'}\n <input\n id={inputId}\n type=\"file\"\n accept=\"image/*\"\n className=\"sr-only\"\n disabled={isDisabled}\n aria-invalid={hasError || undefined}\n aria-describedby={describedBy}\n onChange={(e) => {\n const file = e.target.files?.[0];\n if (file) onChange(file);\n }}\n />\n </label>\n </div>\n );\n}\n"]}
|