@stamcat/craftsman 0.0.50 → 0.0.51
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/package.json +1 -1
- package/skills/craftsman-utility-functions/SKILL.md +13 -1
- package/src/components/Button/AGENTS.md +1 -1
- package/src/components/DateTimePicker/AGENTS.md +1 -3
- package/src/components/Input/AGENTS.md +2 -1
- package/src/components/Input/Input.css +1 -1
- package/src/components/Input/Input.esm.js +4 -3
- package/src/components/Input/Input.scss +0 -1
- package/src/components/Input/InputWrapper.d.ts +1 -0
- package/src/components/InputPassword/AGENTS.md +1 -1
- package/src/components/InputPhone/AGENTS.md +3 -0
- package/src/components/InputPhone/InputPhone.esm.js +7 -6
- package/src/components/Loader/AGENTS.md +1 -1
- package/src/components/Modal/AGENTS.md +1 -1
- package/src/components/Select/AGENTS.md +2 -0
- package/src/components/Select/Select.esm.js +6 -5
- package/src/components/Textarea/AGENTS.md +2 -0
- package/src/components/Textarea/Textarea.esm.js +5 -4
- package/src/components/Tooltip/AGENTS.md +1 -3
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ The package exports an `isEmpty` utility. **Always use it instead of writing inl
|
|
|
14
14
|
Import:
|
|
15
15
|
|
|
16
16
|
```ts
|
|
17
|
-
import { isEmpty } from "@stamcat/craftsman/utilities
|
|
17
|
+
import { isEmpty } from "@stamcat/craftsman/utilities";
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
It returns `true` for:
|
|
@@ -39,6 +39,18 @@ if (Object.keys(obj).length === 0) { ... }
|
|
|
39
39
|
if (arr.length === 0) { ... }
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
## Other validation/formatting utilities
|
|
43
|
+
|
|
44
|
+
Also exported from `@stamcat/craftsman/utilities` (all from `src/utilities/validations.ts`):
|
|
45
|
+
|
|
46
|
+
- `validateEmail(value: string): boolean` — simple regex-based email format check.
|
|
47
|
+
- `getUnitLabel(locale, unit, override?): string` — resolves an `Intl.NumberFormat` unit display label, or returns `override` if provided.
|
|
48
|
+
- `is24HourFormat(locale): boolean` — detects whether the locale's resolved hour cycle is 24-hour (`h23`/`h24`).
|
|
49
|
+
- `getAmPmLabels(locale): [string, string]` — returns the locale's AM/PM strings via `Intl.DateTimeFormat`.
|
|
50
|
+
- `formatTime(value, locale): string` — formats a `Date` (or `DateTimePickerProps["value"]`) as a 24-hour `HH:mm` string, or `"--:--"` if not a valid `Date`.
|
|
51
|
+
|
|
52
|
+
These back the `TimePicker`/`DateTimePicker` locale-aware formatting — prefer them over hand-rolled `Intl` calls in those contexts.
|
|
53
|
+
|
|
42
54
|
## Style utilities parity (`color`, `width`, `breakpoint`)
|
|
43
55
|
|
|
44
56
|
These utility patterns exist in both TypeScript and Sass. See the [craftsman-style-utilities skill](../craftsman-style-utilities/SKILL.md) for full usage details.
|
|
@@ -9,7 +9,7 @@ import { Button } from "@stamcat/craftsman/Button";
|
|
|
9
9
|
Props:
|
|
10
10
|
|
|
11
11
|
- Inherits all native `<button>` props.
|
|
12
|
-
- `variant?: "primary" | "default" | "text"` (default: `"default"`)
|
|
12
|
+
- `variant?: "primary" | "secondary" | "default" | "text"` (default: `"default"`)
|
|
13
13
|
- `size?: number` (default visual scale is `1`)
|
|
14
14
|
- `styles?: React.CSSProperties` (inline style override)
|
|
15
15
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# DateTimePicker — Agent Usage Notes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Import (source-relative, once exported):
|
|
3
|
+
Import:
|
|
6
4
|
|
|
7
5
|
```tsx
|
|
8
6
|
import { DateTimePicker } from "@stamcat/craftsman/DateTimePicker";
|
|
@@ -13,7 +13,8 @@ Props:
|
|
|
13
13
|
- `labelPosition?: "top" | "left" | "bottom" | "right" | "inside" | "hidden"` (default: `"top"`)
|
|
14
14
|
- `error?: string | boolean | ReactNode`
|
|
15
15
|
- `required?: boolean`
|
|
16
|
-
- `
|
|
16
|
+
- `style?: React.CSSProperties` — targets the wrapper element.
|
|
17
|
+
- `inputStyle?: React.CSSProperties` — targets the `<input>` field directly.
|
|
17
18
|
- `type?: TextInputType` (checkbox/radio excluded)
|
|
18
19
|
|
|
19
20
|
Behavior notes:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@layer cf-base{.input-pre-adornment,.input-end-adornment{z-index:1;align-items:center;margin-top:0;display:inline-flex;position:absolute;top:50%;transform:translateY(-50%)}.input-wrapper label .input-label{margin-bottom:calc(var(--w-gutter) * .25)}.input-wrapper[data-required=true] label .input-label:after{content:"*";color:var(--red700)}.input-wrapper[data-label-position=left] label{align-items:center;display:inline-flex}.input-wrapper[data-label-position=left] label .input-label{width:fit-content;margin-right:calc(var(--w-gutter) * .75)}.input-wrapper[data-label-position=left].textarea label{align-items:flex-start}.input-wrapper[data-label-position=bottom] label{flex-direction:column-reverse;align-items:flex-start;display:inline-flex}.input-wrapper[data-label-position=bottom] label .input-label{margin-bottom:0;margin-top:calc(var(--w-gutter) * .25)}.input-wrapper[data-label-position=right] label{flex-direction:row-reverse;align-items:baseline;display:inline-flex}.input-wrapper[data-label-position=right] label .input-label{margin-left:calc(var(--w-gutter) * .5);margin-bottom:0}.input-wrapper[data-label-position=inside]{--react-international-phone-height:50px;--react-international-phone-border-radius:8px;--react-international-phone-border-color:silver;position:relative}.input-wrapper[data-label-position=inside] .input-label{background-color:var(--white);z-index:1;width:fit-content;top:calc(var(--w-gutter) * .45);left:calc(var(--w-gutter) * .25);pointer-events:none;padding:0 5px;transition:top .15s,left .15s,font-size .15s,color .15s;position:absolute}.input-wrapper[data-label-position=inside]>label>.input-field>.input,.input-wrapper[data-label-position=inside]>.input{transition:border-color .2s}.input-wrapper[data-label-position=inside]:focus-within .input-label{top:-.5rem;left:calc(var(--w-gutter) * .125);font-size:calc(var(--w-text) * .75);color:var(--blue500)}.input-wrapper[data-label-position=inside]:focus-within .input{border-color:var(--blue500)}.input-wrapper[data-label-position=inside]:focus-within input::placeholder{opacity:1}.input-wrapper[data-label-position=inside][data-has-input=true] .input-label,.input-wrapper[data-label-position=inside].select .input-label{top:-.5rem;left:calc(var(--w-gutter) * .125);font-size:calc(var(--w-text) * .75)}.input-wrapper[data-label-position=inside] .input[type=tel]{width:100%}.input-wrapper[data-label-position=inside] input::placeholder{opacity:0;transition:opacity .2s}.input-field{align-items:center;
|
|
1
|
+
@layer cf-base{.input-pre-adornment,.input-end-adornment{z-index:1;align-items:center;margin-top:0;display:inline-flex;position:absolute;top:50%;transform:translateY(-50%)}.input-wrapper label .input-label{margin-bottom:calc(var(--w-gutter) * .25)}.input-wrapper[data-required=true] label .input-label:after{content:"*";color:var(--red700)}.input-wrapper[data-label-position=left] label{align-items:center;display:inline-flex}.input-wrapper[data-label-position=left] label .input-label{width:fit-content;margin-right:calc(var(--w-gutter) * .75)}.input-wrapper[data-label-position=left].textarea label{align-items:flex-start}.input-wrapper[data-label-position=bottom] label{flex-direction:column-reverse;align-items:flex-start;display:inline-flex}.input-wrapper[data-label-position=bottom] label .input-label{margin-bottom:0;margin-top:calc(var(--w-gutter) * .25)}.input-wrapper[data-label-position=right] label{flex-direction:row-reverse;align-items:baseline;display:inline-flex}.input-wrapper[data-label-position=right] label .input-label{margin-left:calc(var(--w-gutter) * .5);margin-bottom:0}.input-wrapper[data-label-position=inside]{--react-international-phone-height:50px;--react-international-phone-border-radius:8px;--react-international-phone-border-color:silver;position:relative}.input-wrapper[data-label-position=inside] .input-label{background-color:var(--white);z-index:1;width:fit-content;top:calc(var(--w-gutter) * .45);left:calc(var(--w-gutter) * .25);pointer-events:none;padding:0 5px;transition:top .15s,left .15s,font-size .15s,color .15s;position:absolute}.input-wrapper[data-label-position=inside]>label>.input-field>.input,.input-wrapper[data-label-position=inside]>.input{transition:border-color .2s}.input-wrapper[data-label-position=inside]:focus-within .input-label{top:-.5rem;left:calc(var(--w-gutter) * .125);font-size:calc(var(--w-text) * .75);color:var(--blue500)}.input-wrapper[data-label-position=inside]:focus-within .input{border-color:var(--blue500)}.input-wrapper[data-label-position=inside]:focus-within input::placeholder{opacity:1}.input-wrapper[data-label-position=inside][data-has-input=true] .input-label,.input-wrapper[data-label-position=inside].select .input-label{top:-.5rem;left:calc(var(--w-gutter) * .125);font-size:calc(var(--w-text) * .75)}.input-wrapper[data-label-position=inside] .input[type=tel]{width:100%}.input-wrapper[data-label-position=inside] input::placeholder{opacity:0;transition:opacity .2s}.input-field{align-items:center;display:flex;position:relative}.input-field[data-has-end-adornment=true]>.input{padding-right:calc(var(--w-gutter) * 1.5)}.input-field[data-has-pre-adornment=true]>.input{padding-left:calc(var(--w-gutter) * 2.25)}.input-end-adornment{right:calc(var(--w-gutter) * .5)}.input-pre-adornment{left:calc(var(--w-gutter) * .5)}.input-error{margin-top:calc(var(--w-gutter) * .25);font-size:calc(var(--w-text) * .75);color:var(--red600)}.input-view-toggle{align-items:"center";padding:0;line-height:1;display:inline-flex}}
|
|
@@ -6,7 +6,7 @@ import r from "clsx";
|
|
|
6
6
|
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/Input/Input.tsx
|
|
8
8
|
var o = (o) => {
|
|
9
|
-
let { label: s, id: c, labelPosition: l = "top", required: u = !1, type: d = "text", error: f, endAdornment: p, preAdornment: m, style: h, className: g, value: _, defaultValue: v,
|
|
9
|
+
let { label: s, id: c, labelPosition: l = "top", required: u = !1, type: d = "text", error: f, endAdornment: p, preAdornment: m, style: h, className: g, value: _, defaultValue: v, inputStyle: y, ...b } = o, x = n(), S = c || x;
|
|
10
10
|
return /* @__PURE__ */ i(t, {
|
|
11
11
|
className: g,
|
|
12
12
|
label: s,
|
|
@@ -26,12 +26,13 @@ var o = (o) => {
|
|
|
26
26
|
children: m
|
|
27
27
|
}),
|
|
28
28
|
/* @__PURE__ */ i("input", {
|
|
29
|
-
id:
|
|
29
|
+
id: S,
|
|
30
30
|
type: d,
|
|
31
31
|
className: r("input", d),
|
|
32
32
|
value: _,
|
|
33
33
|
defaultValue: v,
|
|
34
|
-
|
|
34
|
+
style: y,
|
|
35
|
+
...b
|
|
35
36
|
}),
|
|
36
37
|
!e(p) && /* @__PURE__ */ i("span", {
|
|
37
38
|
className: "input-end-adornment",
|
|
@@ -4,6 +4,7 @@ export type LabeledInput = {
|
|
|
4
4
|
labelPosition?: LabelPosition;
|
|
5
5
|
error?: string | boolean | React.ReactNode;
|
|
6
6
|
required?: boolean;
|
|
7
|
+
inputStyle?: React.CSSProperties;
|
|
7
8
|
endAdornment?: React.ReactNode;
|
|
8
9
|
preAdornment?: React.ReactNode;
|
|
9
10
|
onLabelClick?: React.MouseEventHandler<HTMLLabelElement>;
|
|
@@ -9,7 +9,7 @@ import { InputPassword } from "@stamcat/craftsman/InputPassword";
|
|
|
9
9
|
Props:
|
|
10
10
|
|
|
11
11
|
- Extends `Input` props, with `type` constrained to password mode.
|
|
12
|
-
- Includes all label, error, required, and
|
|
12
|
+
- Includes all label, error, required, and style props from `Input`, including `style` (wrapper) and `inputStyle` (targets the `<input>` field directly).
|
|
13
13
|
|
|
14
14
|
Behavior notes:
|
|
15
15
|
|
|
@@ -9,6 +9,7 @@ import { InputPhone } from "@stamcat/craftsman/InputPhone";
|
|
|
9
9
|
Props:
|
|
10
10
|
|
|
11
11
|
- Extends `PhoneInputProps` from `react-international-phone`.
|
|
12
|
+
- `id?: string` — auto-generated via `useId` if not provided.
|
|
12
13
|
- `label?: string | ReactNode`
|
|
13
14
|
- `labelPosition?: "top" | "left" | "bottom" | "right" | "inside" | "hidden"` (default: `"top"`)
|
|
14
15
|
- `error?: string | boolean | ReactNode`
|
|
@@ -16,6 +17,8 @@ Props:
|
|
|
16
17
|
- `defaultCountry?: string` (default: `"us"`)
|
|
17
18
|
- `preferredCountries?: string[]`
|
|
18
19
|
- `endAdornment?: ReactNode`
|
|
20
|
+
- `style?: React.CSSProperties` — targets the wrapper element.
|
|
21
|
+
- `inputStyle?: React.CSSProperties` — targets the underlying phone `<input>` field directly.
|
|
19
22
|
|
|
20
23
|
Behavior notes:
|
|
21
24
|
|
|
@@ -6,10 +6,10 @@ import r from "clsx";
|
|
|
6
6
|
import { jsx as i } from "react/jsx-runtime";
|
|
7
7
|
import { PhoneInput as a } from "react-international-phone";
|
|
8
8
|
//#region src/components/InputPhone/InputPhone.tsx
|
|
9
|
-
var o = ({ label: o, id: s, labelPosition: c = "top", required: l = !1, defaultCountry: u = "us", error: d, style: f, className: p,
|
|
10
|
-
let
|
|
9
|
+
var o = ({ label: o, id: s, labelPosition: c = "top", required: l = !1, defaultCountry: u = "us", error: d, style: f, className: p, inputStyle: m, ...h }) => {
|
|
10
|
+
let g = n(), _ = s || g, v = t.useRef(null);
|
|
11
11
|
return /* @__PURE__ */ i(e, {
|
|
12
|
-
id:
|
|
12
|
+
id: _,
|
|
13
13
|
className: r("inputPhone", p),
|
|
14
14
|
label: o,
|
|
15
15
|
labelPosition: c,
|
|
@@ -17,13 +17,14 @@ var o = ({ label: o, id: s, labelPosition: c = "top", required: l = !1, defaultC
|
|
|
17
17
|
required: l,
|
|
18
18
|
style: f,
|
|
19
19
|
onLabelClick: (e) => {
|
|
20
|
-
let t =
|
|
20
|
+
let t = v.current, n = e.target;
|
|
21
21
|
!t || n === t || n.closest("button") || (e.preventDefault(), t.focus());
|
|
22
22
|
},
|
|
23
23
|
children: /* @__PURE__ */ i(a, {
|
|
24
24
|
defaultCountry: u,
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
inputStyle: m,
|
|
26
|
+
...h,
|
|
27
|
+
ref: v
|
|
27
28
|
})
|
|
28
29
|
});
|
|
29
30
|
};
|
|
@@ -22,7 +22,7 @@ Props:
|
|
|
22
22
|
- `"factory"`
|
|
23
23
|
- `color?: string` (default: `"black"`)
|
|
24
24
|
- `width?: number` (optional, variant-dependent default behavior)
|
|
25
|
-
- `
|
|
25
|
+
- `style?: React.CSSProperties` (native inherited style, merged after the computed loader style variables)
|
|
26
26
|
|
|
27
27
|
Example:
|
|
28
28
|
|
|
@@ -16,7 +16,7 @@ Props:
|
|
|
16
16
|
- `backgroundDismiss?: boolean` (default behavior: `true`)
|
|
17
17
|
- `hideDismissIcon?: boolean` (default behavior: close icon is shown)
|
|
18
18
|
- `footer?: React.ReactNode`
|
|
19
|
-
- `
|
|
19
|
+
- `style?: React.CSSProperties` (native inherited style, applies to outer modal wrapper)
|
|
20
20
|
|
|
21
21
|
Behavior notes:
|
|
22
22
|
|
|
@@ -13,6 +13,8 @@ Props:
|
|
|
13
13
|
- `labelPosition?: "top" | "left" | "bottom" | "right" | "inside" | "hidden"` (default: `"top"`)
|
|
14
14
|
- `error?: string | boolean | ReactNode`
|
|
15
15
|
- `required?: boolean`
|
|
16
|
+
- `style?: React.CSSProperties` — targets the wrapper element.
|
|
17
|
+
- `inputStyle?: React.CSSProperties` — targets the `<select>` field directly.
|
|
16
18
|
- `options?: Array<{ label: string; value: string }>`
|
|
17
19
|
|
|
18
20
|
Behavior notes:
|
|
@@ -4,8 +4,8 @@ import { useId as t } from "react";
|
|
|
4
4
|
import n from "clsx";
|
|
5
5
|
import { jsx as r } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/Select/Select.tsx
|
|
7
|
-
var i = ({ label: i, id: a, labelPosition: o = "top", required: s = !1, error: c, options: l = [], className: u, style: d,
|
|
8
|
-
let
|
|
7
|
+
var i = ({ label: i, id: a, labelPosition: o = "top", required: s = !1, error: c, options: l = [], className: u, style: d, inputStyle: f, ...p }) => {
|
|
8
|
+
let m = t(), h = a || m;
|
|
9
9
|
return /* @__PURE__ */ r(e, {
|
|
10
10
|
label: i,
|
|
11
11
|
labelPosition: o,
|
|
@@ -14,12 +14,13 @@ var i = ({ label: i, id: a, labelPosition: o = "top", required: s = !1, error: c
|
|
|
14
14
|
className: n("select", u),
|
|
15
15
|
style: d,
|
|
16
16
|
children: /* @__PURE__ */ r("select", {
|
|
17
|
-
id:
|
|
18
|
-
|
|
17
|
+
id: h,
|
|
18
|
+
style: f,
|
|
19
|
+
...p,
|
|
19
20
|
children: l.map((e, t) => /* @__PURE__ */ r("option", {
|
|
20
21
|
value: e.value,
|
|
21
22
|
children: e.label
|
|
22
|
-
}, `${
|
|
23
|
+
}, `${h}-opt-${t}`))
|
|
23
24
|
})
|
|
24
25
|
});
|
|
25
26
|
};
|
|
@@ -13,6 +13,8 @@ Props:
|
|
|
13
13
|
- `labelPosition?: "top" | "left" | "bottom" | "right" | "inside" | "hidden"` (default: `"top"`)
|
|
14
14
|
- `error?: string | boolean | ReactNode`
|
|
15
15
|
- `required?: boolean`
|
|
16
|
+
- `style?: React.CSSProperties` — targets the wrapper element.
|
|
17
|
+
- `inputStyle?: React.CSSProperties` — targets the `<textarea>` field directly.
|
|
16
18
|
- `rows?: number`
|
|
17
19
|
|
|
18
20
|
Behavior notes:
|
|
@@ -3,8 +3,8 @@ import { InputWrapper as e } from "../Input/InputWrapper.esm.js";
|
|
|
3
3
|
import { useId as t } from "react";
|
|
4
4
|
import { jsx as n } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/Textarea/Textarea.tsx
|
|
6
|
-
var r = ({ labelPosition: r = "top", id: i, label: a, error: o, required: s, className: c, style: l, value: u, defaultValue: d,
|
|
7
|
-
let
|
|
6
|
+
var r = ({ labelPosition: r = "top", id: i, label: a, error: o, required: s, className: c, style: l, value: u, defaultValue: d, inputStyle: f, ...p }) => {
|
|
7
|
+
let m = t();
|
|
8
8
|
return /* @__PURE__ */ n(e, {
|
|
9
9
|
label: a,
|
|
10
10
|
className: c,
|
|
@@ -15,11 +15,12 @@ var r = ({ labelPosition: r = "top", id: i, label: a, error: o, required: s, cla
|
|
|
15
15
|
value: u,
|
|
16
16
|
defaultValue: d,
|
|
17
17
|
children: /* @__PURE__ */ n("textarea", {
|
|
18
|
-
id: i ||
|
|
18
|
+
id: i || m,
|
|
19
19
|
className: "input",
|
|
20
20
|
value: u,
|
|
21
21
|
defaultValue: d,
|
|
22
|
-
|
|
22
|
+
style: f,
|
|
23
|
+
...p
|
|
23
24
|
})
|
|
24
25
|
});
|
|
25
26
|
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Tooltip — Agent Usage Notes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Import (source-relative, once exported):
|
|
3
|
+
Import:
|
|
6
4
|
|
|
7
5
|
```tsx
|
|
8
6
|
import { Tooltip } from "@stamcat/craftsman/Tooltip";
|