@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamcat/craftsman",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "type": "module",
5
5
  "description": "A powerful, lightweight framework for design systems",
6
6
  "repository": {
@@ -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/validations";
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
- > Not yet part of the documented public export surface (see `craftsman-component-usage` skill's Known Limitations). Source path only — do not assume `@stamcat/craftsman/DateTimePicker` is published until confirmed in `exports.ts`.
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
- - `styles?: React.CSSProperties` (wrapper override)
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;width:fit-content;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}}
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, ...y } = o, b = n(), x = c || b;
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: x,
29
+ id: S,
30
30
  type: d,
31
31
  className: r("input", d),
32
32
  value: _,
33
33
  defaultValue: v,
34
- ...y
34
+ style: y,
35
+ ...b
35
36
  }),
36
37
  !e(p) && /* @__PURE__ */ i("span", {
37
38
  className: "input-end-adornment",
@@ -147,7 +147,6 @@
147
147
  position: relative;
148
148
  display: flex;
149
149
  align-items: center;
150
- width: fit-content;
151
150
  &[data-has-end-adornment="true"] {
152
151
  > .input {
153
152
  padding-right: #{u.width("gutter", 1.5)};
@@ -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 wrapper style props from `Input`.
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, ...m }) => {
10
- let h = n(), g = s || h, _ = t.useRef(null);
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: g,
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 = _.current, n = e.target;
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
- ...m,
26
- ref: _
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
- - `styles?: React.CSSProperties` (inline style override)
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
- - `styles?: React.CSSProperties` (applies to outer modal wrapper)
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, ...f }) => {
8
- let p = t(), m = a || p;
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: m,
18
- ...f,
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
- }, `${m}-opt-${t}`))
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, ...f }) => {
7
- let p = t();
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 || p,
18
+ id: i || m,
19
19
  className: "input",
20
20
  value: u,
21
21
  defaultValue: d,
22
- ...f
22
+ style: f,
23
+ ...p
23
24
  })
24
25
  });
25
26
  };
@@ -1,8 +1,6 @@
1
1
  # Tooltip — Agent Usage Notes
2
2
 
3
- > Not yet part of the documented public export surface (see `craftsman-component-usage` skill's Known Limitations). Source path only — do not assume `@stamcat/craftsman/Tooltip` is published until confirmed in `exports.ts`.
4
-
5
- Import (source-relative, once exported):
3
+ Import:
6
4
 
7
5
  ```tsx
8
6
  import { Tooltip } from "@stamcat/craftsman/Tooltip";