@waveinno/ui 0.7.0 → 0.9.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +1 -1
  3. package/README.md +20 -7
  4. package/bin/starter-files.d.mts +8 -0
  5. package/bin/starter-files.mjs +136 -0
  6. package/bin/waveinno-ui.mjs +466 -0
  7. package/dist/components/ui/bd-address.d.ts +30 -0
  8. package/dist/components/ui/bd-address.js +1 -0
  9. package/dist/components/ui/checkbox-card.d.ts +18 -0
  10. package/dist/components/ui/checkbox-card.js +1 -0
  11. package/dist/components/ui/context-menu.d.ts +32 -0
  12. package/dist/components/ui/context-menu.js +1 -0
  13. package/dist/components/ui/index.d.ts +9 -0
  14. package/dist/components/ui/index.js +1 -1
  15. package/dist/components/ui/input.d.ts +5 -0
  16. package/dist/components/ui/input.js +1 -1
  17. package/dist/components/ui/multi-select.d.ts +39 -0
  18. package/dist/components/ui/multi-select.js +1 -0
  19. package/dist/components/ui/nid-input.d.ts +23 -0
  20. package/dist/components/ui/nid-input.js +1 -0
  21. package/dist/components/ui/payment-method-picker.d.ts +34 -0
  22. package/dist/components/ui/payment-method-picker.js +1 -0
  23. package/dist/components/ui/phone-input.d.ts +26 -0
  24. package/dist/components/ui/phone-input.js +1 -0
  25. package/dist/components/ui/plate-input.d.ts +32 -0
  26. package/dist/components/ui/plate-input.js +1 -0
  27. package/dist/components/ui/sheet.d.ts +8 -3
  28. package/dist/components/ui/sheet.js +1 -1
  29. package/dist/components/ui/taka-input.d.ts +26 -0
  30. package/dist/components/ui/taka-input.js +1 -0
  31. package/dist/lib/bangla.d.ts +50 -0
  32. package/dist/lib/bangla.js +1 -0
  33. package/dist/lib/bd-geo.d.ts +39 -0
  34. package/dist/lib/bd-geo.js +1 -0
  35. package/dist/lib/index.d.ts +2 -0
  36. package/dist/lib/index.js +1 -1
  37. package/dist/styles.css +11 -0
  38. package/package.json +20 -3
@@ -0,0 +1,39 @@
1
+ import * as React from "react";
2
+ import { type InputState } from "./input";
3
+ /**
4
+ * MultiSelect — choose several from a list too long for checkboxes: sites a
5
+ * manager can see, payment methods a gate accepts.
6
+ *
7
+ * The field shows the choices as chips (removable in place) and "+3" past
8
+ * `maxShown`. The list searches as you type, keeps `group`s under headings,
9
+ * and is driven from the keyboard: ↑ ↓ to move, Enter or Space to toggle, Esc
10
+ * to close; Backspace in an empty search removes the last choice. `max` stops
11
+ * further choices with a note rather than silently ignoring the click.
12
+ */
13
+ export interface MultiSelectOption {
14
+ value: string;
15
+ label: string;
16
+ description?: string;
17
+ icon?: React.ReactNode;
18
+ /** A heading the option is listed under. */
19
+ group?: string;
20
+ disabled?: boolean;
21
+ }
22
+ export interface MultiSelectProps {
23
+ options: MultiSelectOption[];
24
+ value?: string[];
25
+ defaultValue?: string[];
26
+ onValueChange?: (value: string[]) => void;
27
+ placeholder?: string;
28
+ /** Chips shown in the field before "+N". Default 3. */
29
+ maxShown?: number;
30
+ max?: number;
31
+ /** Offer "Select all" and "Clear" under the list. Default true. */
32
+ bulk?: boolean;
33
+ disabled?: boolean;
34
+ state?: InputState;
35
+ labels?: Partial<Record<"search" | "empty" | "selectAll" | "clear" | "selected" | "limit" | "remove", string>>;
36
+ "aria-label"?: string;
37
+ className?: string;
38
+ }
39
+ export declare function MultiSelect({ options, value, defaultValue, onValueChange, placeholder, maxShown, max, bulk, disabled, state, labels, "aria-label": ariaLabel, className, }: MultiSelectProps): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{Fragment as Y,jsx as t,jsxs as r}from"react/jsx-runtime";import{Check as _,ChevronDown as U,Search as B,X as G}from"lucide-react";import*as l from"react";import{cn as g}from"../../lib/utils.js";import{fieldStateClass as Q}from"./input.js";import{Popover as W,PopoverAnchor as X,PopoverContent as j}from"./popover.js";const J={search:"Search\u2026",empty:"Nothing matches",selectAll:"Select all",clear:"Clear",selected:"selected",limit:"Up to {max}",remove:"Remove"};function ne({options:w,value:k,defaultValue:R=[],onValueChange:z,placeholder:I="Choose\u2026",maxShown:h=3,max:i,bulk:C=!0,disabled:p,state:L="default",labels:O,"aria-label":q,className:K}){const s={...J,...O},[T,V]=l.useState(R),a=k??T,[m,v]=l.useState(!1),[f,S]=l.useState(""),[c,b]=l.useState(0),x=l.useId(),D=l.useRef(null),u=e=>{k===void 0&&V(e),z?.(e)},M=i!==void 0&&a.length>=i,A=e=>{a.includes(e)?u(a.filter(n=>n!==e)):M||u([...a,e])},y=f.trim().toLowerCase(),o=w.filter(e=>!y||`${e.label} ${e.description??""}`.toLowerCase().includes(y)),E=new Map(w.map(e=>[e.value,e]));l.useEffect(()=>b(0),[y,m]),l.useEffect(()=>{D.current?.querySelector(`[data-index="${c}"]`)?.scrollIntoView({block:"nearest"})},[c]);const F=e=>{if(e.key==="ArrowDown")e.preventDefault(),b(n=>Math.min(o.length-1,n+1));else if(e.key==="ArrowUp")e.preventDefault(),b(n=>Math.max(0,n-1));else if(e.key==="Enter"||e.key===" "&&!f){e.preventDefault();const n=o[c];n&&!n.disabled&&A(n.value)}else e.key==="Backspace"&&!f&&a.length&&u(a.slice(0,-1))};let P;return r(W,{open:m,onOpenChange:e=>(v(e),e||S("")),children:[t(X,{asChild:!0,children:r("div",{role:"combobox",tabIndex:p?-1:0,"aria-expanded":m,"aria-controls":x,"aria-haspopup":"listbox","aria-label":q,"aria-disabled":p||void 0,"data-open":m,onClick:()=>!p&&v(e=>!e),onKeyDown:e=>{!p&&(e.key==="Enter"||e.key===" "||e.key==="ArrowDown")&&(e.preventDefault(),v(!0))},className:g("flex min-h-ctl w-full cursor-pointer items-center gap-1.5 rounded-ctl border bg-raised py-1 pl-1.5 pr-2.5 text-[13px] outline-none transition-[border-color,box-shadow]",Q[L],"data-[open=true]:border-primary data-[open=true]:ring-2 data-[open=true]:ring-primary/20",p&&"cursor-not-allowed bg-hover opacity-70",K),children:[t("span",{className:"flex min-w-0 flex-1 flex-wrap items-center gap-1",children:a.length?r(Y,{children:[a.slice(0,h).map(e=>r("span",{className:"inline-flex h-6 max-w-[160px] items-center gap-1 rounded-[7px] bg-primary-subtle pl-2 pr-0.5 text-[12px] font-medium text-primary animate-[wm-pop_180ms_var(--ease)]",children:[t("span",{className:"truncate",children:E.get(e)?.label??e}),t("button",{type:"button","aria-label":`${s.remove} ${E.get(e)?.label??e}`,disabled:p,onClick:n=>{n.stopPropagation(),u(a.filter(d=>d!==e))},className:"grid size-5 cursor-pointer place-items-center rounded-[5px] hover:bg-primary/15",children:t(G,{className:"size-3"})})]},e)),a.length>h?r("span",{className:"rounded-[7px] bg-hover px-2 py-0.5 text-[12px] font-medium text-muted",children:["+",a.length-h]}):null]}):t("span",{className:"px-1.5 text-placeholder",children:I})}),t(U,{className:g("size-4 shrink-0 text-muted transition-transform duration-200",m&&"rotate-180")})]})}),r(j,{className:"w-[var(--radix-popover-trigger-width)] min-w-56 p-0",onOpenAutoFocus:e=>(e.preventDefault(),e.currentTarget.querySelector("input")?.focus()),children:[r("div",{className:"flex items-center gap-2 border-b border-border px-3",children:[t(B,{className:"size-4 shrink-0 text-muted"}),t("input",{value:f,onChange:e=>S(e.target.value),onKeyDown:F,placeholder:s.search,"aria-label":s.search,"aria-controls":x,"aria-activedescendant":o[c]?`${x}-${o[c].value}`:void 0,className:"h-10 min-w-0 flex-1 bg-transparent text-[13px] text-text outline-none placeholder:text-placeholder"})]}),t("div",{ref:D,id:x,role:"listbox","aria-multiselectable":!0,className:"max-h-64 overflow-y-auto p-1",children:o.length?o.map((e,n)=>{const d=a.includes(e.value),N=e.disabled||M&&!d,$=e.group&&e.group!==P?e.group:null;return P=e.group,r(l.Fragment,{children:[$?t("p",{className:"px-2.5 pb-1 pt-2.5 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted",children:$}):null,r("div",{id:`${x}-${e.value}`,role:"option","aria-selected":d,"aria-disabled":N||void 0,"data-index":n,onMouseEnter:()=>b(n),onMouseDown:H=>H.preventDefault(),onClick:()=>!N&&A(e.value),className:g("flex cursor-pointer items-center gap-2.5 rounded-[8px] px-2.5 py-2 text-[13px] text-text",n===c&&"bg-hover",N&&"cursor-not-allowed opacity-45"),children:[t("span",{"aria-hidden":!0,className:g("grid size-4 shrink-0 place-items-center rounded-[5px] border transition-colors",d?"border-primary bg-primary text-white":"border-border2 bg-raised"),children:d?t(_,{className:"size-3",strokeWidth:3}):null}),e.icon?t("span",{className:"grid shrink-0 place-items-center text-muted [&_svg]:size-4",children:e.icon}):null,r("span",{className:"min-w-0 flex-1",children:[t("span",{className:"block truncate",children:e.label}),e.description?t("span",{className:"block truncate text-[11.5px] text-muted",children:e.description}):null]})]})]},e.value)}):t("p",{className:"px-3 py-6 text-center text-[13px] text-muted",children:s.empty})}),C||i!==void 0?r("div",{className:"flex items-center justify-between border-t border-border px-3 py-2 text-[12px]",children:[r("span",{className:"text-muted",children:[a.length," ",s.selected,i!==void 0?` \xB7 ${s.limit.replace("{max}",String(i))}`:""]}),C?r("span",{className:"flex gap-3 font-medium",children:[i===void 0?t("button",{type:"button",onClick:()=>u([...new Set([...a,...o.filter(e=>!e.disabled).map(e=>e.value)])]),className:"cursor-pointer text-primary hover:underline",children:s.selectAll}):null,t("button",{type:"button",disabled:!a.length,onClick:()=>u([]),className:"cursor-pointer text-muted hover:text-text disabled:opacity-40",children:s.clear})]}):null]}):null]})]})}export{ne as MultiSelect};
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ import { type InputState } from "./input";
3
+ /**
4
+ * NidInput — a Bangladeshi national ID number.
5
+ *
6
+ * Takes digits only (Bangla digits too) and groups them as they are printed:
7
+ * the 10-digit smart card as 123 456 7890, the older 13- and 17-digit numbers
8
+ * in fours. It names the kind once the length matches one and ticks; any other
9
+ * length is incomplete, never silently accepted. `onValueChange` gets the bare
10
+ * digits and whether they form a valid length.
11
+ */
12
+ export type NidKind = "smart" | "old13" | "old17";
13
+ export interface NidInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type"> {
14
+ value?: string;
15
+ defaultValue?: string;
16
+ onValueChange?: (digits: string, info: {
17
+ valid: boolean;
18
+ kind: NidKind | null;
19
+ }) => void;
20
+ state?: InputState;
21
+ labels?: Partial<Record<NidKind, string>>;
22
+ }
23
+ export declare const NidInput: React.ForwardRefExoticComponent<NidInputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as a,jsxs as f}from"react/jsx-runtime";import{Check as y,IdCard as w}from"lucide-react";import*as c from"react";import{cn as g}from"../../lib/utils.js";import{fieldStateClass as C}from"./input.js";const d={10:"smart",13:"old13",17:"old17"},m={smart:"Smart card",old13:"Old NID",old17:"Old NID, with birth year"},p=e=>e.replace(/[০-৯]/g,l=>String("\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF".indexOf(l))).replace(/\D/g,"").slice(0,17);function S(e){const l=e.length<=10?[3,3,4]:e.length<=13?[4,4,5]:[4,4,4,5],s=[];let n=0;for(const r of l){if(n>=e.length)break;s.push(e.slice(n,n+r)),n+=r}return s.join(" ")}const K=c.forwardRef(({value:e,defaultValue:l="",onValueChange:s,state:n="default",labels:r,className:h,disabled:u,placeholder:x="123 456 7890",...N},b)=>{const[I,k]=c.useState(()=>p(l)),i=e!==void 0?p(e):I,t=d[i.length]??null;return f("div",{className:g("@container flex h-ctl w-full items-center gap-2 rounded-ctl border bg-raised px-3 shadow-[inset_0_1px_1px_rgba(5,8,22,.04)] transition-[border-color,box-shadow]",C[n],u&&"cursor-not-allowed bg-hover opacity-70",h),children:[a(w,{className:"size-4 shrink-0 text-muted"}),a("input",{ref:b,type:"text",inputMode:"numeric",autoComplete:"off",disabled:u,placeholder:x,value:S(i),onChange:v=>{const o=p(v.target.value);e===void 0&&k(o),s?.(o,{valid:!!d[o.length],kind:d[o.length]??null})},className:"num h-full min-w-[14ch] flex-1 bg-transparent text-[13.5px] tracking-[0.04em] text-text outline-none placeholder:tracking-normal placeholder:text-placeholder",...N}),i?f("span",{title:t?r?.[t]??m[t]:void 0,className:g("inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium",t?"bg-success-subtle text-success @max-[420px]:px-1.5":"bg-hover text-muted"),children:[t?a(y,{className:"size-3",strokeWidth:3}):null,t?a("span",{className:"@max-[420px]:sr-only",children:r?.[t]??m[t]}):`${i.length} / ${i.length<10?10:i.length<13?13:17}`]}):null]})});K.displayName="NidInput";export{K as NidInput};
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ /**
3
+ * PaymentMethodPicker — how a customer in Bangladesh pays: bKash, Nagad,
4
+ * Rocket, Upay, a card, cash, a bank transfer, cash on delivery.
5
+ *
6
+ * Wallets get a lettered tile in their own colour (no logos, so nothing to
7
+ * license); the rest get an icon. Pass `methods` to choose and order them, as
8
+ * ids or as your own `{ id, name, description, mark }`, and `fees` to show a
9
+ * charge or a discount beside each one. Looks: `cards` (default), `list` (one
10
+ * column) or `chips` (one row, for a summary sidebar). Arrow keys move the
11
+ * choice, as with any radio group.
12
+ */
13
+ export type PaymentMethodId = "bkash" | "nagad" | "rocket" | "upay" | "card" | "cash" | "bank" | "cod";
14
+ export interface PaymentMethod {
15
+ id: string;
16
+ name: string;
17
+ description?: string;
18
+ /** A tile: an icon, or the colour and letter of a wallet. */
19
+ mark: React.ReactNode;
20
+ }
21
+ export declare const PAYMENT_METHODS: Record<PaymentMethodId, PaymentMethod>;
22
+ export interface PaymentMethodPickerProps {
23
+ value?: string;
24
+ defaultValue?: string;
25
+ onValueChange?: (id: string) => void;
26
+ methods?: (PaymentMethodId | PaymentMethod)[];
27
+ /** Beside each method: "No fee", "1.85%", "−৳50". */
28
+ fees?: Partial<Record<string, React.ReactNode>>;
29
+ variant?: "cards" | "list" | "chips";
30
+ disabled?: boolean;
31
+ "aria-label"?: string;
32
+ className?: string;
33
+ }
34
+ export declare function PaymentMethodPicker({ value, defaultValue, onValueChange, methods, fees, variant, disabled, "aria-label": ariaLabel, className, }: PaymentMethodPickerProps): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as a,jsxs as M}from"react/jsx-runtime";import{Banknote as b,Check as k,CreditCard as f,Landmark as y,Truck as P}from"lucide-react";import*as h from"@radix-ui/react-radio-group";import{cn as o}from"../../lib/utils.js";import{RadioCard as x,RadioGroup as v}from"./radio-group.js";const i=(r,t)=>a("span",{"aria-hidden":!0,className:"grid size-full place-items-center rounded-[inherit] text-[13px] font-bold text-white",style:{background:t},children:r}),R={bkash:{id:"bkash",name:"bKash",description:"Mobile wallet",mark:i("b","#e2136e")},nagad:{id:"nagad",name:"Nagad",description:"Mobile wallet",mark:i("N","#ec1c24")},rocket:{id:"rocket",name:"Rocket",description:"Dutch-Bangla mobile banking",mark:i("R","#8c3494")},upay:{id:"upay",name:"Upay",description:"Mobile wallet",mark:i("U","#00579f")},card:{id:"card",name:"Card",description:"Visa, Mastercard, Amex",mark:a(f,{})},cash:{id:"cash",name:"Cash",description:"Pay at the counter",mark:a(b,{})},bank:{id:"bank",name:"Bank transfer",description:"BEFTN or NPSB",mark:a(y,{})},cod:{id:"cod",name:"Cash on delivery",description:"Pay when it arrives",mark:a(P,{})}};function _({value:r,defaultValue:t,onValueChange:n,methods:u=["bkash","nagad","card","cash"],fees:g,variant:s="cards",disabled:c,"aria-label":l="Payment method",className:p}){const d=u.map(e=>typeof e=="string"?R[e]:e),m=t??(r===void 0?d[0]?.id:void 0);return s==="chips"?a(h.Root,{value:r,defaultValue:m,onValueChange:n,disabled:c,"aria-label":l,orientation:"horizontal",className:o("flex flex-wrap gap-2",p),children:d.map(e=>M(h.Item,{value:e.id,className:o("group inline-flex h-10 cursor-pointer items-center gap-2 rounded-full border border-border bg-raised py-1 pl-1 pr-3.5 text-[13px] font-medium text-text outline-none transition-[border-color,box-shadow,background-color]","hover:border-border2 focus-visible:ring-2 focus-visible:ring-primary/35 disabled:cursor-not-allowed disabled:opacity-50","data-[state=checked]:border-primary data-[state=checked]:bg-primary-subtle data-[state=checked]:shadow-[0_0_0_3px_var(--primary-subtle)]"),children:[a("span",{className:"grid size-8 place-items-center overflow-hidden rounded-full bg-surface text-muted [&_svg]:size-4",children:e.mark}),e.name,a(k,{className:"size-3.5 text-primary opacity-0 transition-opacity group-data-[state=checked]:opacity-100",strokeWidth:3})]},e.id))}):a(v,{value:r,defaultValue:m,onValueChange:n,disabled:c,"aria-label":l,className:o(s==="cards"?"grid gap-2.5 sm:grid-cols-2":"grid gap-2",p),children:d.map(e=>a(x,{value:e.id,title:e.name,description:e.description,aside:g?.[e.id],icon:a("span",{className:"grid size-full place-items-center overflow-hidden rounded-[inherit] [&_svg]:size-[18px]",children:e.mark}),className:"[&>span:first-child]:overflow-hidden [&>span:first-child]:p-0"},e.id))})}export{R as PAYMENT_METHODS,_ as PaymentMethodPicker};
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import { type BdPhone } from "../../lib/bd-geo";
3
+ import { type InputState } from "./input";
4
+ /**
5
+ * PhoneInput — a Bangladeshi mobile number, typed any way people type it.
6
+ *
7
+ * "01712345678", "+880 1712-345678", "8801712345678" and Bangla digits all
8
+ * read the same; the field shows +880 and groups the rest as people write it.
9
+ * It names the operator from the prefix as you type (013/017 Grameenphone,
10
+ * 014/019 Banglalink, 016/018 Robi, 015 Teletalk) and ticks when the number is
11
+ * complete. `onValueChange` gets "+8801712345678" (or "" until it is whole) and
12
+ * the parsed details, so a form never stores the formatting.
13
+ */
14
+ export interface PhoneInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type"> {
15
+ /** Any form of the number; "+8801712345678" is what you get back. */
16
+ value?: string;
17
+ defaultValue?: string;
18
+ onValueChange?: (e164: string, phone: BdPhone) => void;
19
+ /** Show the operator on the right as you type. Default true. */
20
+ showOperator?: boolean;
21
+ state?: InputState;
22
+ labels?: {
23
+ invalidPrefix?: string;
24
+ };
25
+ }
26
+ export declare const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as t,jsxs as d}from"react/jsx-runtime";import{Check as k}from"lucide-react";import*as h from"react";import{BD_OPERATOR_NAMES as b,parseBdPhone as o}from"../../lib/bd-geo.js";import{cn as g}from"../../lib/utils.js";import{fieldStateClass as C}from"./input.js";const M=e=>e.length>4?`${e.slice(0,4)}-${e.slice(4)}`:e,R=h.forwardRef(({value:e,defaultValue:v="",onValueChange:u,showOperator:P=!0,state:x="default",labels:c,className:N,disabled:m,placeholder:w="1712-345678",...I},y)=>{const s=e!==void 0,[_,f]=h.useState(()=>o(v).national),i=s?o(e).national:_,n=o(i),a=i.length>=2&&!n.operator;return d("div",{className:g("@container flex h-ctl w-full items-center overflow-hidden rounded-ctl border bg-raised text-[13px] shadow-[inset_0_1px_1px_rgba(5,8,22,.04)] transition-[border-color,box-shadow]",C[a?"error":x],m&&"cursor-not-allowed bg-hover opacity-70",N),children:[d("span",{className:"flex h-full shrink-0 items-center gap-1.5 border-r border-border bg-surface px-2.5 text-muted @max-[230px]:px-2",children:[t("span",{"aria-hidden":!0,className:"relative h-3 w-[18px] overflow-hidden rounded-[2px] bg-[#006a4e] @max-[230px]:hidden",children:t("span",{className:"absolute left-[38%] top-1/2 size-[7px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#f42a41]"})}),t("span",{className:"num font-medium text-text",children:"+880"})]}),t("input",{ref:y,type:"tel",inputMode:"tel",autoComplete:"tel-national",disabled:m,placeholder:w,"aria-invalid":a||x==="error"||void 0,value:M(i),onChange:l=>{const r=o(l.target.value);s||f(r.national),u?.(r.e164,r)},onPaste:l=>{const r=l.clipboardData.getData("text");if(!r)return;l.preventDefault();const p=o(r);s||f(p.national),u?.(p.e164,p)},className:"num h-full min-w-[10ch] flex-1 bg-transparent px-3 text-text outline-none placeholder:text-placeholder disabled:cursor-not-allowed @max-[230px]:px-2",...I}),P&&(n.operator||a)?d("span",{title:a?c?.invalidPrefix??"Not a mobile prefix":b[n.operator],className:g("mr-2 inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium @max-[290px]:min-h-5 @max-[290px]:px-1.5",a?"bg-danger-subtle text-danger":n.valid?"bg-success-subtle text-success":"bg-hover text-muted"),children:[n.valid?t(k,{className:"size-3",strokeWidth:3}):t("span",{"aria-hidden":!0,className:"hidden size-1.5 rounded-full bg-current @max-[290px]:block"}),t("span",{className:"@max-[290px]:sr-only",children:a?c?.invalidPrefix??"Not a mobile prefix":b[n.operator]})]}):null]})});R.displayName="PhoneInput";export{R as PhoneInput};
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ import { type InputState } from "./input";
3
+ /**
4
+ * PlateInput — a Bangladeshi number plate: where it is registered, its class
5
+ * letter and the six digits, with the plate itself drawn beside the fields.
6
+ *
7
+ * The value is the plate as it is written in English, "DHAKA METRO-GA
8
+ * 12-3456"; the drawing shows it in Bangla script (ঢাকা মেট্রো-গ ১২-৩৪৫৬), the
9
+ * way it is on the car, or in English with `plateScript="latin"`. The number
10
+ * field takes digits only and adds the dash. `onValueChange` gets the plate
11
+ * (or "" until it is complete) and its parts.
12
+ */
13
+ export interface PlateParts {
14
+ area: string;
15
+ cls: string;
16
+ number: string;
17
+ }
18
+ export interface PlateInputProps {
19
+ value?: string;
20
+ defaultValue?: string;
21
+ onValueChange?: (plate: string, parts: PlateParts & {
22
+ valid: boolean;
23
+ }) => void;
24
+ /** Draw the plate next to the fields. Default true. */
25
+ preview?: boolean;
26
+ plateScript?: "bn" | "latin";
27
+ state?: InputState;
28
+ disabled?: boolean;
29
+ className?: string;
30
+ }
31
+ export declare function parsePlate(plate: string): PlateParts;
32
+ export declare function PlateInput({ value, defaultValue, onValueChange, preview, plateScript, state, disabled, className }: PlateInputProps): React.JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as a,jsxs as r}from"react/jsx-runtime";import*as $ from"react";import{toBanglaDigits as E}from"../../lib/bangla.js";import{BD_DISTRICTS as D}from"../../lib/bd-geo.js";import{cn as m}from"../../lib/utils.js";import{fieldStateClass as L}from"./input.js";import{Select as b,SelectContent as x,SelectGroup as A,SelectItem as u,SelectLabel as S,SelectTrigger as f,SelectValue as h}from"./select.js";const g=[["DHAKA METRO","\u09A2\u09BE\u0995\u09BE \u09AE\u09C7\u099F\u09CD\u09B0\u09CB"],["CHATTA METRO","\u099A\u099F\u09CD\u099F \u09AE\u09C7\u099F\u09CD\u09B0\u09CB"],["KHULNA METRO","\u0996\u09C1\u09B2\u09A8\u09BE \u09AE\u09C7\u099F\u09CD\u09B0\u09CB"],["RAJSHAHI METRO","\u09B0\u09BE\u099C\u09B6\u09BE\u09B9\u09C0 \u09AE\u09C7\u099F\u09CD\u09B0\u09CB"],["SYLHET METRO","\u09B8\u09BF\u09B2\u09C7\u099F \u09AE\u09C7\u099F\u09CD\u09B0\u09CB"],["BARISHAL METRO","\u09AC\u09B0\u09BF\u09B6\u09BE\u09B2 \u09AE\u09C7\u099F\u09CD\u09B0\u09CB"]],H=[...g,...D.map(l=>[l.en.toUpperCase(),l.bn])],O=new Map(H),_=[["KA","\u0995"],["KHA","\u0996"],["GA","\u0997"],["GHA","\u0998"],["CHA","\u099A"],["CHHA","\u099B"],["JA","\u099C"],["JHA","\u099D"],["TA","\u09A4"],["THA","\u09A5"],["DA","\u09A6"],["DHA","\u09A7"],["NA","\u09A8"],["PA","\u09AA"],["BHA","\u09AD"],["MA","\u09AE"],["LA","\u09B2"],["SHA","\u09B6"],["SA","\u09B8"],["HA","\u09B9"]],k=new Map(_);function v(l){const s=/^(.+)-([A-Z]+) (\d{2})-?(\d{0,4})$/.exec(l.trim().toUpperCase());return s?{area:s[1],cls:s[2],number:`${s[3]}${s[4]}`}:{area:"DHAKA METRO",cls:"GA",number:""}}function J({value:l,defaultValue:s="",onValueChange:P,preview:T=!0,plateScript:c="bn",state:C="default",disabled:i,className:N}){const[R,I]=$.useState(()=>v(s)),t=l!==void 0?v(l):R,o=t.number.length>2?`${t.number.slice(0,2)}-${t.number.slice(2)}`:t.number,p=e=>{const n={...t,...e};l===void 0&&I(n);const d=n.number.length===6;P?.(d?`${n.area}-${n.cls} ${n.number.slice(0,2)}-${n.number.slice(2)}`:"",{...n,valid:d})},w=c==="bn"?`${O.get(t.area)??t.area}-${k.get(t.cls)??t.cls}`:`${t.area}-${t.cls}`,M=t.number?c==="bn"?E(o):o:c==="bn"?"\u09E7\u09E8-\u09E9\u09EA\u09EB\u09EC":"12-3456";return r("div",{className:m("flex flex-wrap items-center gap-3",N),children:[r("div",{className:"grid min-w-0 flex-1 grid-cols-[minmax(0,1.6fr)_minmax(0,0.9fr)_minmax(0,1fr)] gap-2",children:[r(b,{value:t.area,onValueChange:e=>p({area:e}),disabled:i,children:[a(f,{"aria-label":"Registered in",children:a(h,{})}),r(x,{className:"max-h-72",children:[r(A,{children:[a(S,{children:"Metro"}),g.map(([e])=>a(u,{value:e,children:e},e))]}),r(A,{children:[a(S,{children:"District"}),H.slice(g.length).map(([e])=>a(u,{value:e,children:e},e))]})]})]}),r(b,{value:t.cls,onValueChange:e=>p({cls:e}),disabled:i,children:[a(f,{"aria-label":"Class",children:a(h,{})}),a(x,{className:"max-h-72",children:_.map(([e,n])=>r(u,{value:e,children:[e," \xB7 ",n]},e))})]}),a("div",{className:m("flex h-ctl items-center rounded-ctl border bg-raised px-3 transition-[border-color,box-shadow]",L[C],i&&"opacity-70"),children:a("input",{"aria-label":"Number",inputMode:"numeric",disabled:i,placeholder:"12-3456",value:o,onChange:e=>p({number:e.target.value.replace(/[০-৯]/g,n=>String("\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF".indexOf(n))).replace(/\D/g,"").slice(0,6)}),className:"num h-full w-full min-w-0 bg-transparent text-[13.5px] tracking-[0.06em] text-text outline-none placeholder:text-placeholder"})})]}),T?r("div",{"aria-hidden":!0,className:"grid h-[60px] w-[150px] shrink-0 place-items-center rounded-[6px] border-[2.5px] border-[#111] bg-white px-2 text-center leading-none text-[#111] shadow-[inset_0_0_0_2px_#fff,inset_0_0_0_3px_#111,0_4px_12px_-4px_rgba(5,8,22,.35)]",children:[a("span",{className:"w-full truncate text-[12.5px] font-bold",children:w}),a("span",{className:m("num text-[21px] font-bold tracking-[0.04em]",!t.number&&"text-[#111]/30"),children:M})]}):null]})}export{J as PlateInput,v as parsePlate};
@@ -1,16 +1,21 @@
1
1
  import * as DialogPrimitive from "@radix-ui/react-dialog";
2
2
  import * as React from "react";
3
3
  /**
4
- * Right-side sheet — the physical basis of the record drawer (§2.2).
4
+ * Sheet — a panel that slides in from an edge: the record drawer (§2.2) on
5
+ * the right, navigation on the left, and on a phone a bottom sheet.
5
6
  *
6
7
  * Modal by default: the page behind is blurred and inert until the sheet is
7
- * closed (Escape, the close button or a click on the blurred page).
8
+ * closed (Escape, the close button or a click on the blurred page). A bottom
9
+ * sheet has a handle and can also be dragged down to close; on a wide screen
10
+ * it becomes a card centred above the bottom edge.
8
11
  */
9
12
  export declare const Sheet: React.FC<DialogPrimitive.DialogProps>;
10
13
  export declare const SheetTrigger: React.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
11
14
  export declare const SheetClose: React.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
15
+ export type SheetSide = "right" | "left" | "bottom";
12
16
  export declare const SheetContent: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
13
- side?: "right" | "left";
17
+ side?: SheetSide;
18
+ /** Width classes for a side sheet. Default "w-full sm:w-drawer". */
14
19
  width?: string;
15
20
  scrim?: boolean;
16
21
  hideClose?: boolean;
@@ -1 +1 @@
1
- "use client";import{jsx as t,jsxs as r}from"react/jsx-runtime";import*as e from"@radix-ui/react-dialog";import{X as d}from"lucide-react";import*as p from"react";import{cn as g}from"../../lib/utils.js";const u=e.Root,x=e.Trigger,v=e.Close,b=p.forwardRef(({className:i,children:s,side:o="right",width:a="w-full sm:w-drawer",scrim:l=!0,hideClose:n,...m},c)=>r(e.Portal,{children:[l?t(e.Overlay,{"data-scrim":"",className:"fixed inset-0 z-50 bg-[rgba(5,8,22,.42)] backdrop-blur-[3px]"}):null,r(e.Content,{ref:c,"data-sheet":o,className:g("fixed inset-y-0 z-50 flex max-w-full flex-col border-border bg-raised shadow-elev3 outline-none",o==="right"?"right-0 max-sm:border-l sm:inset-y-2 sm:right-2 sm:rounded-[calc(16px*var(--radius-scale))] sm:border":"left-0 max-sm:border-r sm:inset-y-2 sm:left-2 sm:rounded-[calc(16px*var(--radius-scale))] sm:border",a,i),...m,children:[s,n?null:r(e.Close,{className:"absolute right-4 top-4 z-10 grid size-8 cursor-pointer place-items-center rounded-full border border-border bg-raised/80 text-muted backdrop-blur transition-[background-color,color,transform] hover:bg-hover hover:text-text active:scale-95",children:[t(d,{className:"size-4"}),t("span",{className:"sr-only",children:"Close"})]})]})]}));b.displayName="SheetContent";const C=e.Title,P=e.Description;export{u as Sheet,v as SheetClose,b as SheetContent,P as SheetDescription,C as SheetTitle,x as SheetTrigger};
1
+ "use client";import{jsx as s,jsxs as m}from"react/jsx-runtime";import*as t from"@radix-ui/react-dialog";import{X as v}from"lucide-react";import*as l from"react";import{cn as f}from"../../lib/utils.js";const w=t.Root,T=t.Trigger,N=t.Close,S={right:"inset-y-0 right-0 max-sm:border-l sm:inset-y-2 sm:right-2 sm:rounded-[calc(16px*var(--radius-scale))] sm:border",left:"inset-y-0 left-0 max-sm:border-r sm:inset-y-2 sm:left-2 sm:rounded-[calc(16px*var(--radius-scale))] sm:border",bottom:"inset-x-0 bottom-0 max-h-[92dvh] rounded-t-[calc(22px*var(--radius-scale))] border-t pb-[env(safe-area-inset-bottom)] sm:inset-x-auto sm:bottom-3 sm:left-1/2 sm:w-[min(640px,calc(100%-24px))] sm:-translate-x-1/2 sm:rounded-[calc(20px*var(--radius-scale))] sm:border"},P=110,R=l.forwardRef(({className:p,children:b,side:i="right",width:g,scrim:h=!0,hideClose:c,...x},a)=>{const o=l.useRef(null),u=l.useRef(null),n=l.useRef(null),y=e=>{o.current=e,typeof a=="function"?a(e):a&&(a.current=e)},d=i==="bottom"?{onPointerDown:e=>{n.current={y:e.clientY,dy:0},e.currentTarget.setPointerCapture(e.pointerId),o.current&&(o.current.style.transition="none")},onPointerMove:e=>{if(!n.current||!o.current)return;const r=Math.max(0,e.clientY-n.current.y);n.current.dy=r,o.current.style.transform=`translateY(${r}px)`},onPointerUp:()=>{const e=n.current?.dy??0;n.current=null;const r=o.current;r&&(r.style.transition="transform 260ms cubic-bezier(.22,1,.36,1)",e>P?(r.style.transform="translateY(110%)",window.setTimeout(()=>u.current?.click(),180)):r.style.transform="")}}:null;return m(t.Portal,{children:[h?s(t.Overlay,{"data-scrim":"",className:"fixed inset-0 z-50 bg-[rgba(5,8,22,.42)] backdrop-blur-[3px]"}):null,m(t.Content,{ref:y,"data-sheet":i,className:f("fixed z-50 flex max-w-full flex-col border-border bg-raised shadow-elev3 outline-none",S[i],g??(i==="bottom"?null:"w-full sm:w-drawer"),p),...x,children:[d?s("div",{...d,className:"flex shrink-0 cursor-grab touch-none justify-center pb-1 pt-2.5 active:cursor-grabbing",children:s("span",{"aria-hidden":!0,className:"h-1.5 w-11 rounded-full bg-border2"})}):null,b,m(t.Close,{ref:u,className:f("absolute right-4 top-4 z-10 grid size-8 cursor-pointer place-items-center rounded-full border border-border bg-raised/80 text-muted backdrop-blur transition-[background-color,color,transform] hover:bg-hover hover:text-text active:scale-95",c&&"sr-only"),tabIndex:c?-1:void 0,"aria-hidden":c||void 0,children:[s(v,{className:"size-4"}),s("span",{className:"sr-only",children:"Close"})]})]})]})});R.displayName="SheetContent";const E=t.Title,M=t.Description;export{w as Sheet,N as SheetClose,R as SheetContent,M as SheetDescription,E as SheetTitle,T as SheetTrigger};
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import { type InputState } from "./input";
3
+ /**
4
+ * TakaInput — an amount in taka, grouped the Bangladeshi way as you type
5
+ * (12,34,567, not 1,234,567), with the amount in words under it: "12.35 lakh".
6
+ *
7
+ * It holds a number, never a string: `onValueChange` gets 1234567 (or null when
8
+ * empty). Typing keeps the cursor where it was while commas come and go, Bangla
9
+ * digits are read, and `min`/`max` clamp on blur, not while typing, so nobody
10
+ * is fought mid-number. `decimals` allows paisa.
11
+ */
12
+ export interface TakaInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "min" | "max"> {
13
+ value?: number | null;
14
+ defaultValue?: number | null;
15
+ onValueChange?: (value: number | null) => void;
16
+ min?: number;
17
+ max?: number;
18
+ /** Digits after the point. 0 (default) for whole taka, 2 for paisa. */
19
+ decimals?: number;
20
+ /** The amount in words under the field: "latin" (12.35 lakh), "bn" (১২.৩৫ লাখ) or false. */
21
+ words?: "latin" | "bn" | false;
22
+ /** The sign before the number. Default "৳". */
23
+ symbol?: React.ReactNode;
24
+ state?: InputState;
25
+ }
26
+ export declare const TakaInput: React.ForwardRefExoticComponent<TakaInputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as k,jsxs as v}from"react/jsx-runtime";import*as s from"react";import{fromBanglaDigits as $,groupLakh as c,lakhCrore as A}from"../../lib/bangla.js";import{cn as R}from"../../lib/utils.js";import{fieldStateClass as P}from"./input.js";function I(t,a){const l=$(t).replace(/[^\d.]/g,""),[f="",...m]=l.split("."),e=m.join("").slice(0,a);return{int:f.replace(/^0+(?=\d)/,""),frac:e,point:a>0&&l.includes(".")}}function q({int:t,frac:a,point:l}){return!t&&!l?"":`${t?c(Number(t)):"0"}${l?`.${a}`:""}`}const D=s.forwardRef(({value:t,defaultValue:a=null,onValueChange:l,min:f,max:m,decimals:e=0,words:d="latin",symbol:M="\u09F3",state:S="default",className:H,disabled:N,onBlur:L,...C},E)=>{const w=t!==void 0,[x,b]=s.useState(()=>a==null?"":c(a,e)),y=s.useRef(null);s.useImperativeHandle(E,()=>y.current),s.useEffect(()=>{if(!w)return;const r=I(x,e);(r.int||r.frac?+`${r.int||0}.${r.frac||0}`:null)!==t&&b(t==null?"":c(t,e))},[t,w,e]);const p=I(x,e),o=p.int||p.frac?+`${p.int||0}.${p.frac||0}`:null;return v("div",{className:R("w-full",H),children:[v("div",{className:R("flex h-ctl w-full items-center rounded-ctl border bg-raised shadow-[inset_0_1px_1px_rgba(5,8,22,.04)] transition-[border-color,box-shadow]",P[S],N&&"cursor-not-allowed bg-hover opacity-70"),children:[k("span",{className:"pl-3 text-[14px] font-medium text-muted",children:M}),k("input",{ref:y,type:"text",inputMode:e?"decimal":"numeric",disabled:N,value:x,onChange:r=>{const n=r.target,h=n.selectionStart??n.value.length,_=$(n.value.slice(0,h)).replace(/[^\d.]/g,"").length,u=I(n.value,e),g=q(u);b(g);const B=u.int||u.frac?+`${u.int||0}.${u.frac||0}`:null;l?.(B),requestAnimationFrame(()=>{let i=0,T=0;for(;i<g.length&&T<_;)/[\d.]/.test(g[i])&&T++,i++;n.setSelectionRange(i,i)})},onBlur:r=>{if(o!=null){const n=Math.min(m??1/0,Math.max(f??-1/0,o)),h=e?c(n,e):c(n);b(h),n!==o&&l?.(n)}L?.(r)},className:"num h-full min-w-0 flex-1 bg-transparent px-2 text-[14px] text-text outline-none placeholder:text-placeholder disabled:cursor-not-allowed",...C})]}),d&&o!=null&&o>=1e3?v("p",{"aria-live":"polite",className:"mt-1 text-[12px] text-muted",children:[A(o,{script:d}),d==="latin"?" taka":" \u099F\u09BE\u0995\u09BE"]}):null]})});D.displayName="TakaInput";export{D as TakaInput};
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Bangla helpers: digits, the Bangla calendar (Bongabdo) and lakh/crore.
3
+ *
4
+ * The calendar is the one Bangladesh uses since the 2019 revision: the year
5
+ * starts on 14 April (Pohela Boishakh); Boishakh to Ashwin have 31 days,
6
+ * Falgun has 29 (30 when February has 29), every other month 30. So 16
7
+ * December is always 1 Poush and 21 February always 8 Falgun.
8
+ */
9
+ /** "2026" → "২০২৬". Leaves everything that is not a digit alone. */
10
+ export declare function toBanglaDigits(input: string | number): string;
11
+ /** "২০২৬" → "2026", for reading what someone typed on a Bangla keyboard. */
12
+ export declare function fromBanglaDigits(input: string): string;
13
+ export declare const BANGLA_MONTHS: readonly ["বৈশাখ", "জ্যৈষ্ঠ", "আষাঢ়", "শ্রাবণ", "ভাদ্র", "আশ্বিন", "কার্তিক", "অগ্রহায়ণ", "পৌষ", "মাঘ", "ফাল্গুন", "চৈত্র"];
14
+ export declare const BANGLA_MONTHS_LATIN: readonly ["Boishakh", "Joishtho", "Asharh", "Shrabon", "Bhadro", "Ashwin", "Kartik", "Ogrohayon", "Poush", "Magh", "Falgun", "Choitro"];
15
+ export declare const BANGLA_WEEKDAYS: readonly ["রবিবার", "সোমবার", "মঙ্গলবার", "বুধবার", "বৃহস্পতিবার", "শুক্রবার", "শনিবার"];
16
+ /** The six seasons, two months each, from Boishakh. */
17
+ export declare const BANGLA_SEASONS: readonly ["গ্রীষ্ম", "বর্ষা", "শরৎ", "হেমন্ত", "শীত", "বসন্ত"];
18
+ export interface BongabdoDate {
19
+ /** Bangla year, e.g. 1433. */
20
+ year: number;
21
+ /** 0 = Boishakh … 11 = Choitro. */
22
+ month: number;
23
+ /** 1-based day of the month. */
24
+ day: number;
25
+ monthName: string;
26
+ monthNameLatin: string;
27
+ season: string;
28
+ }
29
+ /** The Bangla date of a day (its local calendar date; the time is ignored). */
30
+ export declare function toBongabdo(date: Date): BongabdoDate;
31
+ export interface FormatBanglaOptions {
32
+ /** Add the weekday: "শনিবার, ১৮ আশ্বিন ১৪৩৩". */
33
+ weekday?: boolean;
34
+ /** "bn" (default) for Bangla script and digits, "latin" for "18 Ashwin 1433". */
35
+ script?: "bn" | "latin";
36
+ /** Add the year. Default true. */
37
+ year?: boolean;
38
+ }
39
+ /** "১৮ আশ্বিন ১৪৩৩", or "18 Ashwin 1433" with `script: "latin"`. */
40
+ export declare function formatBanglaDate(date: Date, { weekday, script, year }?: FormatBanglaOptions): string;
41
+ /**
42
+ * A large amount in words the way Bangladesh says it: 3,20,000 → "3.2 lakh",
43
+ * 1,25,00,000 → "1.25 crore". Under a lakh it returns the grouped number.
44
+ */
45
+ export declare function lakhCrore(n: number, { script, digits }?: {
46
+ script?: "latin" | "bn";
47
+ digits?: number;
48
+ }): string;
49
+ /** 1234567 → "12,34,567": groups of two after the first three, as on every BD receipt. */
50
+ export declare function groupLakh(n: number, decimals?: number): string;
@@ -0,0 +1 @@
1
+ const c="\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF";function i(t){return String(t).replace(/[0-9]/g,n=>c[Number(n)])}function p(t){return t.replace(/[০-৯]/g,n=>String(c.indexOf(n)))}const u=["\u09AC\u09C8\u09B6\u09BE\u0996","\u099C\u09CD\u09AF\u09C8\u09B7\u09CD\u09A0","\u0986\u09B7\u09BE\u09A2\u09BC","\u09B6\u09CD\u09B0\u09BE\u09AC\u09A3","\u09AD\u09BE\u09A6\u09CD\u09B0","\u0986\u09B6\u09CD\u09AC\u09BF\u09A8","\u0995\u09BE\u09B0\u09CD\u09A4\u09BF\u0995","\u0985\u0997\u09CD\u09B0\u09B9\u09BE\u09AF\u09BC\u09A3","\u09AA\u09CC\u09B7","\u09AE\u09BE\u0998","\u09AB\u09BE\u09B2\u09CD\u0997\u09C1\u09A8","\u099A\u09C8\u09A4\u09CD\u09B0"],l=["Boishakh","Joishtho","Asharh","Shrabon","Bhadro","Ashwin","Kartik","Ogrohayon","Poush","Magh","Falgun","Choitro"],m=["\u09B0\u09AC\u09BF\u09AC\u09BE\u09B0","\u09B8\u09CB\u09AE\u09AC\u09BE\u09B0","\u09AE\u0999\u09CD\u0997\u09B2\u09AC\u09BE\u09B0","\u09AC\u09C1\u09A7\u09AC\u09BE\u09B0","\u09AC\u09C3\u09B9\u09B8\u09CD\u09AA\u09A4\u09BF\u09AC\u09BE\u09B0","\u09B6\u09C1\u0995\u09CD\u09B0\u09AC\u09BE\u09B0","\u09B6\u09A8\u09BF\u09AC\u09BE\u09B0"],b=["\u0997\u09CD\u09B0\u09C0\u09B7\u09CD\u09AE","\u09AC\u09B0\u09CD\u09B7\u09BE","\u09B6\u09B0\u09CE","\u09B9\u09C7\u09AE\u09A8\u09CD\u09A4","\u09B6\u09C0\u09A4","\u09AC\u09B8\u09A8\u09CD\u09A4"],h=t=>t%4===0&&t%100!==0||t%400===0,g=(t,n,s)=>Date.UTC(t,n,s)/864e5;function $(t){const n=t.getFullYear(),s=g(n,t.getMonth(),t.getDate()),r=s>=g(n,3,14)?n:n-1;let e=s-g(r,3,14);const o=[31,31,31,31,31,31,30,30,30,30,h(r+1)?30:29,30];let a=0;for(;e>=o[a];)e-=o[a],a++;return{year:r-593,month:a,day:e+1,monthName:u[a],monthNameLatin:l[a],season:b[Math.floor(a/2)]}}function f(t,{weekday:n=!1,script:s="bn",year:r=!0}={}){const e=$(t);if(s==="latin"){const a=`${e.day} ${e.monthNameLatin}${r?` ${e.year}`:""}`;return n?`${t.toLocaleDateString("en-GB",{weekday:"long"})}, ${a}`:a}const o=`${i(e.day)} ${e.monthName}${r?` ${i(e.year)}`:""}`;return n?`${m[t.getDay()]}, ${o}`:o}function B(t,{script:n="latin",digits:s=2}={}){const r=Math.abs(t),e=a=>String(Number(a.toFixed(s)));let o;return r>=1e7?o=`${e(t/1e7)} ${n==="bn"?"\u0995\u09CB\u099F\u09BF":"crore"}`:r>=1e5?o=`${e(t/1e5)} ${n==="bn"?"\u09B2\u09BE\u0996":"lakh"}`:r>=1e3?o=`${e(t/1e3)} ${n==="bn"?"\u09B9\u09BE\u099C\u09BE\u09B0":"thousand"}`:o=String(t),n==="bn"?i(o):o}function N(t,n=0){const[s,r]=Math.abs(t).toFixed(n).split("."),e=s.slice(-3),o=s.slice(0,-3).replace(/\B(?=(\d{2})+(?!\d))/g,",");return`${t<0?"-":""}${o?`${o},`:""}${e}${r?`.${r}`:""}`}export{u as BANGLA_MONTHS,l as BANGLA_MONTHS_LATIN,b as BANGLA_SEASONS,m as BANGLA_WEEKDAYS,f as formatBanglaDate,p as fromBanglaDigits,N as groupLakh,B as lakhCrore,i as toBanglaDigits,$ as toBongabdo};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Bangladesh's 8 divisions and 64 districts, in English and Bangla, for
3
+ * address forms. Names follow the current official spellings (Chattogram,
4
+ * Cumilla, Barishal, Jashore, Bogura).
5
+ */
6
+ export interface BdPlace {
7
+ id: string;
8
+ en: string;
9
+ bn: string;
10
+ }
11
+ export interface BdDivision extends BdPlace {
12
+ districts: BdPlace[];
13
+ }
14
+ export declare const BD_DIVISIONS: BdDivision[];
15
+ /** Every district, with the id of its division. */
16
+ export declare const BD_DISTRICTS: (BdPlace & {
17
+ division: string;
18
+ })[];
19
+ export declare const bdDivision: (id: string) => BdDivision | undefined;
20
+ export declare const bdDistrict: (id: string) => (BdPlace & {
21
+ division: string;
22
+ }) | undefined;
23
+ export type BdOperator = "grameenphone" | "banglalink" | "robi" | "teletalk";
24
+ export declare const BD_OPERATOR_NAMES: Record<BdOperator, string>;
25
+ export interface BdPhone {
26
+ /** The ten digits after +880, e.g. "1712345678". */
27
+ national: string;
28
+ /** "+8801712345678" when complete, else "". */
29
+ e164: string;
30
+ /** "01712-345678" as people write it. */
31
+ display: string;
32
+ operator: BdOperator | null;
33
+ valid: boolean;
34
+ }
35
+ /**
36
+ * Reads a Bangladeshi mobile number however it was typed: "01712345678",
37
+ * "+880 1712-345678", "8801712345678", in Bangla digits too.
38
+ */
39
+ export declare function parseBdPhone(input: string): BdPhone;
@@ -0,0 +1 @@
1
+ const a=(r,i)=>({id:r.toLowerCase().replace(/[^a-z]+/g,"-").replace(/-$/,""),en:r,bn:i}),e=[{...a("Barishal","\u09AC\u09B0\u09BF\u09B6\u09BE\u09B2"),districts:[a("Barguna","\u09AC\u09B0\u0997\u09C1\u09A8\u09BE"),a("Barishal","\u09AC\u09B0\u09BF\u09B6\u09BE\u09B2"),a("Bhola","\u09AD\u09CB\u09B2\u09BE"),a("Jhalokati","\u099D\u09BE\u09B2\u0995\u09BE\u09A0\u09BF"),a("Patuakhali","\u09AA\u099F\u09C1\u09AF\u09BC\u09BE\u0996\u09BE\u09B2\u09C0"),a("Pirojpur","\u09AA\u09BF\u09B0\u09CB\u099C\u09AA\u09C1\u09B0")]},{...a("Chattogram","\u099A\u099F\u09CD\u099F\u0997\u09CD\u09B0\u09BE\u09AE"),districts:[a("Bandarban","\u09AC\u09BE\u09A8\u09CD\u09A6\u09B0\u09AC\u09BE\u09A8"),a("Brahmanbaria","\u09AC\u09CD\u09B0\u09BE\u09B9\u09CD\u09AE\u09A3\u09AC\u09BE\u09A1\u09BC\u09BF\u09AF\u09BC\u09BE"),a("Chandpur","\u099A\u09BE\u0981\u09A6\u09AA\u09C1\u09B0"),a("Chattogram","\u099A\u099F\u09CD\u099F\u0997\u09CD\u09B0\u09BE\u09AE"),a("Cox's Bazar","\u0995\u0995\u09CD\u09B8\u09AC\u09BE\u099C\u09BE\u09B0"),a("Cumilla","\u0995\u09C1\u09AE\u09BF\u09B2\u09CD\u09B2\u09BE"),a("Feni","\u09AB\u09C7\u09A8\u09C0"),a("Khagrachhari","\u0996\u09BE\u0997\u09A1\u09BC\u09BE\u099B\u09A1\u09BC\u09BF"),a("Lakshmipur","\u09B2\u0995\u09CD\u09B7\u09CD\u09AE\u09C0\u09AA\u09C1\u09B0"),a("Noakhali","\u09A8\u09CB\u09AF\u09BC\u09BE\u0996\u09BE\u09B2\u09C0"),a("Rangamati","\u09B0\u09BE\u0999\u09CD\u0997\u09BE\u09AE\u09BE\u099F\u09BF")]},{...a("Dhaka","\u09A2\u09BE\u0995\u09BE"),districts:[a("Dhaka","\u09A2\u09BE\u0995\u09BE"),a("Faridpur","\u09AB\u09B0\u09BF\u09A6\u09AA\u09C1\u09B0"),a("Gazipur","\u0997\u09BE\u099C\u09C0\u09AA\u09C1\u09B0"),a("Gopalganj","\u0997\u09CB\u09AA\u09BE\u09B2\u0997\u099E\u09CD\u099C"),a("Kishoreganj","\u0995\u09BF\u09B6\u09CB\u09B0\u0997\u099E\u09CD\u099C"),a("Madaripur","\u09AE\u09BE\u09A6\u09BE\u09B0\u09C0\u09AA\u09C1\u09B0"),a("Manikganj","\u09AE\u09BE\u09A8\u09BF\u0995\u0997\u099E\u09CD\u099C"),a("Munshiganj","\u09AE\u09C1\u09A8\u09CD\u09B8\u09C0\u0997\u099E\u09CD\u099C"),a("Narayanganj","\u09A8\u09BE\u09B0\u09BE\u09AF\u09BC\u09A3\u0997\u099E\u09CD\u099C"),a("Narsingdi","\u09A8\u09B0\u09B8\u09BF\u0982\u09A6\u09C0"),a("Rajbari","\u09B0\u09BE\u099C\u09AC\u09BE\u09A1\u09BC\u09C0"),a("Shariatpur","\u09B6\u09B0\u09C0\u09AF\u09BC\u09A4\u09AA\u09C1\u09B0"),a("Tangail","\u099F\u09BE\u0999\u09CD\u0997\u09BE\u0987\u09B2")]},{...a("Khulna","\u0996\u09C1\u09B2\u09A8\u09BE"),districts:[a("Bagerhat","\u09AC\u09BE\u0997\u09C7\u09B0\u09B9\u09BE\u099F"),a("Chuadanga","\u099A\u09C1\u09AF\u09BC\u09BE\u09A1\u09BE\u0999\u09CD\u0997\u09BE"),a("Jashore","\u09AF\u09B6\u09CB\u09B0"),a("Jhenaidah","\u099D\u09BF\u09A8\u09BE\u0987\u09A6\u09B9"),a("Khulna","\u0996\u09C1\u09B2\u09A8\u09BE"),a("Kushtia","\u0995\u09C1\u09B7\u09CD\u099F\u09BF\u09AF\u09BC\u09BE"),a("Magura","\u09AE\u09BE\u0997\u09C1\u09B0\u09BE"),a("Meherpur","\u09AE\u09C7\u09B9\u09C7\u09B0\u09AA\u09C1\u09B0"),a("Narail","\u09A8\u09A1\u09BC\u09BE\u0987\u09B2"),a("Satkhira","\u09B8\u09BE\u09A4\u0995\u09CD\u09B7\u09C0\u09B0\u09BE")]},{...a("Mymensingh","\u09AE\u09AF\u09BC\u09AE\u09A8\u09B8\u09BF\u0982\u09B9"),districts:[a("Jamalpur","\u099C\u09BE\u09AE\u09BE\u09B2\u09AA\u09C1\u09B0"),a("Mymensingh","\u09AE\u09AF\u09BC\u09AE\u09A8\u09B8\u09BF\u0982\u09B9"),a("Netrokona","\u09A8\u09C7\u09A4\u09CD\u09B0\u0995\u09CB\u09A3\u09BE"),a("Sherpur","\u09B6\u09C7\u09B0\u09AA\u09C1\u09B0")]},{...a("Rajshahi","\u09B0\u09BE\u099C\u09B6\u09BE\u09B9\u09C0"),districts:[a("Bogura","\u09AC\u0997\u09C1\u09A1\u09BC\u09BE"),a("Chapainawabganj","\u099A\u09BE\u0981\u09AA\u09BE\u0987\u09A8\u09AC\u09BE\u09AC\u0997\u099E\u09CD\u099C"),a("Joypurhat","\u099C\u09AF\u09BC\u09AA\u09C1\u09B0\u09B9\u09BE\u099F"),a("Naogaon","\u09A8\u0993\u0997\u09BE\u0981"),a("Natore","\u09A8\u09BE\u099F\u09CB\u09B0"),a("Pabna","\u09AA\u09BE\u09AC\u09A8\u09BE"),a("Rajshahi","\u09B0\u09BE\u099C\u09B6\u09BE\u09B9\u09C0"),a("Sirajganj","\u09B8\u09BF\u09B0\u09BE\u099C\u0997\u099E\u09CD\u099C")]},{...a("Rangpur","\u09B0\u0982\u09AA\u09C1\u09B0"),districts:[a("Dinajpur","\u09A6\u09BF\u09A8\u09BE\u099C\u09AA\u09C1\u09B0"),a("Gaibandha","\u0997\u09BE\u0987\u09AC\u09BE\u09A8\u09CD\u09A7\u09BE"),a("Kurigram","\u0995\u09C1\u09A1\u09BC\u09BF\u0997\u09CD\u09B0\u09BE\u09AE"),a("Lalmonirhat","\u09B2\u09BE\u09B2\u09AE\u09A8\u09BF\u09B0\u09B9\u09BE\u099F"),a("Nilphamari","\u09A8\u09C0\u09B2\u09AB\u09BE\u09AE\u09BE\u09B0\u09C0"),a("Panchagarh","\u09AA\u099E\u09CD\u099A\u0997\u09A1\u09BC"),a("Rangpur","\u09B0\u0982\u09AA\u09C1\u09B0"),a("Thakurgaon","\u09A0\u09BE\u0995\u09C1\u09B0\u0997\u09BE\u0981\u0993")]},{...a("Sylhet","\u09B8\u09BF\u09B2\u09C7\u099F"),districts:[a("Habiganj","\u09B9\u09AC\u09BF\u0997\u099E\u09CD\u099C"),a("Moulvibazar","\u09AE\u09CC\u09B2\u09AD\u09C0\u09AC\u09BE\u099C\u09BE\u09B0"),a("Sunamganj","\u09B8\u09C1\u09A8\u09BE\u09AE\u0997\u099E\u09CD\u099C"),a("Sylhet","\u09B8\u09BF\u09B2\u09C7\u099F")]}],o=e.flatMap(r=>r.districts.map(i=>({...i,division:r.id}))),h=r=>e.find(i=>i.id===r),g=r=>o.find(i=>i.id===r),l={13:"grameenphone",17:"grameenphone",14:"banglalink",19:"banglalink",16:"robi",18:"robi",15:"teletalk"},d={grameenphone:"Grameenphone",banglalink:"Banglalink",robi:"Robi",teletalk:"Teletalk"};function p(r){let i=r.replace(/[০-৯]/g,s=>String("\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF".indexOf(s))).replace(/\D/g,"");i.startsWith("880")&&(i=i.slice(3)),i.startsWith("0")&&(i=i.slice(1)),i=i.slice(0,10);const n=l[i.slice(0,2)]??null,t=i.length===10&&n!==null;return{national:i,e164:t?`+880${i}`:"",display:i?`0${i.slice(0,4)}${i.length>4?`-${i.slice(4)}`:""}`:"",operator:n,valid:t}}export{o as BD_DISTRICTS,e as BD_DIVISIONS,d as BD_OPERATOR_NAMES,g as bdDistrict,h as bdDivision,p as parseBdPhone};
@@ -4,3 +4,5 @@ export * from "./format";
4
4
  export * from "./utils";
5
5
  export * from "./boot-script";
6
6
  export * from "./theme";
7
+ export * from "./bangla";
8
+ export * from "./bd-geo";
package/dist/lib/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./currency.js";export*from"./format.js";export*from"./utils.js";export*from"./boot-script.js";export*from"./theme.js";
1
+ export*from"./currency.js";export*from"./format.js";export*from"./utils.js";export*from"./boot-script.js";export*from"./theme.js";export*from"./bangla.js";export*from"./bd-geo.js";
package/dist/styles.css CHANGED
@@ -484,6 +484,17 @@
484
484
  [data-sheet="right"][data-state="closed"] { animation: sheet-out-right 220ms cubic-bezier(0.4, 0, 1, 1) both }
485
485
  [data-sheet="left"][data-state="open"] { animation: sheet-in-left var(--dur) var(--ease) }
486
486
  [data-sheet="left"][data-state="closed"] { animation: sheet-out-left 190ms var(--ease) }
487
+ [data-sheet="bottom"][data-state="open"] { animation: sheet-in-bottom 360ms var(--ease) }
488
+ [data-sheet="bottom"][data-state="closed"] { animation: sheet-out-bottom 220ms cubic-bezier(0.4, 0, 1, 1) both }
489
+
490
+ @keyframes sheet-in-bottom {
491
+ from { transform: translateY(100%) }
492
+ to { transform: none }
493
+ }
494
+ @keyframes sheet-out-bottom {
495
+ from { transform: none }
496
+ to { transform: translateY(100%) }
497
+ }
487
498
 
488
499
  @keyframes sheet-content-in {
489
500
  from { opacity: 0 }
package/package.json CHANGED
@@ -1,14 +1,26 @@
1
1
  {
2
2
  "name": "@waveinno/ui",
3
- "version": "0.7.0",
4
- "description": "WAVEINNO design system — tokens, shell, primitives and patterns shared by every WAVEINNO product",
3
+ "version": "0.9.0",
4
+ "description": "Wavi: the React design system from Bangladesh. 100+ components, app shell, charts, and bKash/taka/Bangla-ready inputs for Next.js and Vite, with Tailwind CSS 4. Set up with npx @waveinno/ui init.",
5
5
  "keywords": [
6
6
  "waveinno",
7
7
  "design-system",
8
8
  "react",
9
9
  "nextjs",
10
10
  "tailwindcss",
11
- "radix-ui"
11
+ "radix-ui",
12
+ "wavi",
13
+ "react-components",
14
+ "component-library",
15
+ "ui-kit",
16
+ "tailwind-css-4",
17
+ "vite",
18
+ "dark-mode",
19
+ "bangladesh",
20
+ "bangla",
21
+ "bkash",
22
+ "taka",
23
+ "dashboard"
12
24
  ],
13
25
  "license": "MIT",
14
26
  "author": "WAVEINNO <info@waveinno.com>",
@@ -40,6 +52,7 @@
40
52
  },
41
53
  "files": [
42
54
  "dist",
55
+ "bin",
43
56
  "README.md",
44
57
  "LICENSE",
45
58
  "CHANGELOG.md"
@@ -66,6 +79,7 @@
66
79
  "@radix-ui/react-accordion": "^1.2.20",
67
80
  "@radix-ui/react-avatar": "^1.2.6",
68
81
  "@radix-ui/react-checkbox": "^1.3.11",
82
+ "@radix-ui/react-context-menu": "^2.3.7",
69
83
  "@radix-ui/react-dialog": "^1.1.23",
70
84
  "@radix-ui/react-dropdown-menu": "^2.1.24",
71
85
  "@radix-ui/react-hover-card": "^1.1.23",
@@ -112,5 +126,8 @@
112
126
  "react-dom": "^19.3.0",
113
127
  "tailwindcss": "^4.3.3",
114
128
  "typescript": "^7.0.2"
129
+ },
130
+ "bin": {
131
+ "waveinno-ui": "bin/waveinno-ui.mjs"
115
132
  }
116
133
  }