@contractspec/lib.design-system 4.0.0 → 4.2.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 (41) hide show
  1. package/README.md +24 -2
  2. package/dist/browser/components/atoms/Input.js +1 -1
  3. package/dist/browser/components/data-view/DataViewRenderer.js +1 -1
  4. package/dist/browser/components/forms/controls/Autocomplete.js +1 -1
  5. package/dist/browser/components/forms/controls/Select.js +1 -1
  6. package/dist/browser/components/forms/controls/select-options.js +1 -0
  7. package/dist/browser/components/molecules/FiltersToolbar.js +1 -1
  8. package/dist/browser/hooks/useListUrlState.js +1 -1
  9. package/dist/browser/theme/tailwind-css.js +5 -5
  10. package/dist/components/atoms/Input.js +1 -1
  11. package/dist/components/data-view/DataViewRenderer.d.ts +3 -3
  12. package/dist/components/data-view/DataViewRenderer.js +1 -1
  13. package/dist/components/data-view/DataViewRenderer.native.d.ts +3 -3
  14. package/dist/components/forms/controls/Autocomplete.d.ts +9 -1
  15. package/dist/components/forms/controls/Autocomplete.js +1 -1
  16. package/dist/components/forms/controls/Autocomplete.native.d.ts +5 -1
  17. package/dist/components/forms/controls/Select.d.ts +7 -5
  18. package/dist/components/forms/controls/Select.js +1 -1
  19. package/dist/components/forms/controls/Select.native.d.ts +7 -5
  20. package/dist/components/forms/controls/index.d.ts +1 -1
  21. package/dist/components/forms/controls/select-options.d.ts +17 -0
  22. package/dist/components/forms/controls/select-options.js +1 -0
  23. package/dist/components/molecules/FiltersToolbar.d.ts +1 -0
  24. package/dist/components/molecules/FiltersToolbar.js +1 -1
  25. package/dist/hooks/useListUrlState.d.ts +3 -1
  26. package/dist/hooks/useListUrlState.js +1 -1
  27. package/dist/native/components/atoms/Input.js +1 -1
  28. package/dist/native/components/atoms/Input.native.js +1 -1
  29. package/dist/native/components/data-view/DataViewRenderer.js +1 -1
  30. package/dist/native/components/data-view/DataViewRenderer.native.js +1 -1
  31. package/dist/native/components/forms/controls/Autocomplete.js +1 -1
  32. package/dist/native/components/forms/controls/Autocomplete.native.js +1 -1
  33. package/dist/native/components/forms/controls/Select.js +1 -1
  34. package/dist/native/components/forms/controls/Select.native.js +1 -1
  35. package/dist/native/components/forms/controls/select-options.js +1 -0
  36. package/dist/native/components/molecules/FiltersToolbar.js +1 -1
  37. package/dist/native/components/molecules/FiltersToolbar.native.js +1 -1
  38. package/dist/native/hooks/useListUrlState.js +1 -1
  39. package/dist/native/theme/tailwind-css.js +5 -5
  40. package/dist/theme/tailwind-css.js +5 -5
  41. package/package.json +19 -7
package/README.md CHANGED
@@ -154,9 +154,25 @@ import {
154
154
  componentKey="Select"
155
155
  options={[{ labelI18n: "status.draft", value: "draft" }]}
156
156
  />
157
+ <Select
158
+ componentKey="Select"
159
+ groups={[
160
+ {
161
+ labelI18n: "status.lifecycle",
162
+ options: [{ labelI18n: "status.draft", value: "draft" }],
163
+ },
164
+ {
165
+ labelI18n: "status.release",
166
+ options: [{ labelI18n: "status.published", value: "published" }],
167
+ },
168
+ ]}
169
+ />
157
170
  </DesignSystemThemeProvider>;
158
171
  ```
159
172
 
173
+ When both `options` and `groups` are provided to `Select`, grouped options are
174
+ used and the flat `options` list is ignored.
175
+
160
176
  ### Use form controls from the design-system boundary
161
177
 
162
178
  The root barrel exposes themed and translation-aware controls for product
@@ -170,6 +186,11 @@ surfaces: `Button`, `Input`, `Textarea`, `Select`, `NativeSelect`,
170
186
  `FieldSeparator` wrappers so contract-driven forms can preserve accessible
171
187
  legend, description, invalid, and grouped-control structure.
172
188
 
189
+ The `Autocomplete` control renders through the shared combobox primitive on web
190
+ and keeps native rendering aligned for query, selected options, loading, error,
191
+ and empty states. FormSpec renderers pass resolver-backed async state through
192
+ these props without requiring product surfaces to know the underlying transport.
193
+
173
194
  ### Render forms on mobile through the shared renderer
174
195
 
175
196
  Use the focused shared renderer subpath when rendering `FormSpec` contracts in
@@ -285,10 +306,11 @@ hidden-column recovery without widening the primitive table API.
285
306
  ### Renderers and hooks
286
307
 
287
308
  - renderer exports from `./renderers`
288
- - form-contract renderer support, including readonly, password, autocomplete, address, phone, date, time, datetime, semantic FormSpec groups, grid layout hints, and text/textarea input groups
309
+ - form-contract renderer support, including readonly, email, password, autocomplete, address, phone, date, time, datetime, semantic FormSpec groups, grid layout hints, progressive FormSpec sections/steps, mobile-safe FormSpec column helper output, and text/textarea/email input groups
289
310
  - translation-aware rendering through `DesignSystemTranslationProvider` and `createTranslationResolver`
290
311
  - theme-aware form controls and stack primitives that consume ThemeSpec component variant props
291
- - hooks such as `useListUrlState`
312
+ - hooks such as `useListUrlState`, including scoped list filters where locked constraints are excluded from user-editable URL state
313
+ - DataViewRenderer filter chips for scoped DataView filters, including disabled locked chips on web and native surfaces
292
314
  - navigation-related shared types
293
315
 
294
316
  ### Component composition layers
@@ -1 +1 @@
1
- import{jsx as N}from"react/jsx-runtime";import{Input as j}from"@contractspec/lib.ui-kit-web/ui/input";import{mapKeyboardToWeb as B}from"../../lib/keyboard";import{useThemedTextField as C}from"../primitives/control";export function Input({value:A,defaultValue:D,onChange:E,onSubmit:O,onFocus:G,onBlur:H,placeholder:J,disabled:M,readOnly:P,maxLength:Q,name:R,className:S,keyboard:U,componentKey:X,themeVariant:Y,placeholderI18n:Z,ariaLabelI18n:$,...z}){const _=B(U),q=C({defaultComponentKey:"Input",componentKey:X,themeVariant:Y,className:S,style:z.style,placeholder:J,placeholderI18n:Z,ariaLabelI18n:$});return N(j,{...q.themed.props,...z,className:q.themed.className,style:q.themed.style,value:A,defaultValue:D,onChange:E,onFocus:G,onBlur:H,placeholder:q.placeholder,"aria-label":q.ariaLabel,disabled:M,readOnly:P,maxLength:Q,name:R,..._})}
1
+ import{jsx as N}from"react/jsx-runtime";import{Input as j}from"@contractspec/lib.ui-kit-web/ui/input";import{mapKeyboardToWeb as B}from"../../lib/keyboard";import{useThemedTextField as C}from"../primitives/control";export function Input({value:D,defaultValue:E,onChange:G,onSubmit:O,onFocus:H,onBlur:J,placeholder:M,disabled:P,readOnly:Q,maxLength:R,name:S,className:U,keyboard:z,componentKey:X,themeVariant:Y,placeholderI18n:Z,ariaLabelI18n:$,...A}){const _=z?B(z):{},q=C({defaultComponentKey:"Input",componentKey:X,themeVariant:Y,className:U,style:A.style,placeholder:M,placeholderI18n:Z,ariaLabelI18n:$});return N(j,{...q.themed.props,..._,...A,className:q.themed.className,style:q.themed.style,value:D,defaultValue:E,onChange:G,onFocus:H,onBlur:J,placeholder:q.placeholder,"aria-label":q.ariaLabel,disabled:P,readOnly:Q,maxLength:R,name:S})}
@@ -1 +1 @@
1
- import{jsx as G,jsxs as w,Fragment as d}from"react/jsx-runtime";import{Pagination as b}from"@contractspec/lib.ui-kit-web/ui/atoms/Pagination";import{VStack as j}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as _}from"@contractspec/lib.ui-kit-web/ui/text";import*as k from"react";import{resolveTranslationString as F,useDesignSystemTranslation as N}from"../../i18n/translation";import{FiltersToolbar as x}from"../molecules/FiltersToolbar";import{DataViewDetail as A}from"./DataViewDetail";import{DataViewList as T}from"./DataViewList";import{DataViewTable as C}from"./DataViewTable";export function DataViewRenderer({spec:E,items:M=[],item:Y=null,className:I,renderActions:Q,onSelect:U,onRowClick:Z,toolbar:$,loading:q,headerActions:O,emptyState:K,footer:z,search:V,onSearchChange:L,filters:J,onFilterChange:P,pagination:H,onPageChange:R}){const W=N(),u=k.useMemo(()=>{switch(E.view.kind){case"list":return G(T,{spec:E,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K});case"table":return G(C,{spec:E,items:M,className:I,onRowClick:Z,toolbar:$,loading:q,emptyState:K,headerActions:O,footer:z});case"detail":return G(A,{spec:E,item:Y,className:I,emptyState:K,headerActions:O});case"grid":{const B=E.view,X={kind:"list",layout:"card",fields:B.fields,filters:B.filters,actions:B.actions,primaryField:B.primaryField,secondaryFields:B.secondaryFields},v={...E,view:X};return G(T,{spec:v,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K})}default:return G(_,{className:I,children:F("Unsupported data view kind",W)})}},[E,M,Y,I,Q,U,Z,$,q,O,K,z,W]);if(!(E.view.kind==="list"||E.view.kind==="table"||E.view.kind==="grid"))return G(d,{children:u});return w(j,{gap:"lg",children:[(E.view.filters?.length||L)&&G(x,{searchValue:V,onSearchChange:L,searchPlaceholder:F("Search...",W)??"Search...",activeChips:J?Object.entries(J).map(([B,X])=>({key:B,label:`${B}: ${X}`,onRemove:()=>{if(J){const{[B]:v,...D}=J;P?.(D)}}})):[],onClearAll:J&&Object.keys(J).length>0?()=>P?.({}):void 0,right:E.view.kind==="table"?void 0:O}),u,H&&H.total>0&&G(b,{currentPage:H.page,totalPages:Math.ceil(H.total/H.pageSize),totalItems:H.total,itemsPerPage:H.pageSize,onPageChange:(B)=>R?.(B),onItemsPerPageChange:(B)=>{R?.(1)},showItemsPerPage:!1})]})}
1
+ import{jsx as J,jsxs as c,Fragment as p}from"react/jsx-runtime";import{resolveDataViewFilters as S}from"@contractspec/lib.contracts-spec/data-views";import{Pagination as d}from"@contractspec/lib.ui-kit-web/ui/atoms/Pagination";import{VStack as h}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as f}from"@contractspec/lib.ui-kit-web/ui/text";import*as L from"react";import{resolveTranslationString as V,useDesignSystemTranslation as u}from"../../i18n/translation";import{FiltersToolbar as m}from"../molecules/FiltersToolbar";import{DataViewDetail as v}from"./DataViewDetail";import{DataViewList as b}from"./DataViewList";import{DataViewTable as y}from"./DataViewTable";export function DataViewRenderer({spec:B,items:G=[],item:X=null,className:M,renderActions:$,onSelect:q,onRowClick:P,toolbar:R,loading:T,headerActions:Y,emptyState:Q,footer:j,search:w,onSearchChange:_,filters:I,onFilterChange:Z,pagination:K,onPageChange:D}){const z=u(),O=L.useMemo(()=>S({filters:B.view.filters,scope:B.view.filterScope,user:g(I)}),[I,B.view.filterScope,B.view.filters]),N=L.useMemo(()=>{if(B.view.filterScope){const E=Object.entries(O.user).map(([H,W])=>({key:H,label:`${A(B,H)}: ${F(W)}`,onRemove:()=>{const{[H]:r,...C}=O.user;Z?.(C)}})),U=O.lockedChips==="hidden"?[]:Object.entries(O.locked).map(([H,W])=>({key:`locked-${H}`,label:`${A(B,H)}: ${F(W)}`,disabled:!0}));return[...E,...U]}return I?Object.entries(I).map(([E,U])=>({key:E,label:`${E}: ${String(U)}`,onRemove:()=>{const{[E]:H,...W}=I;Z?.(W)}})):[]},[I,Z,O,B]),k=B.view.filterScope?Object.keys(O.user).length>0:Boolean(I&&Object.keys(I).length>0),x=L.useMemo(()=>{switch(B.view.kind){case"list":return J(b,{spec:B,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q});case"table":return J(y,{spec:B,items:G,className:M,onRowClick:P,toolbar:R,loading:T,emptyState:Q,headerActions:Y,footer:j});case"detail":return J(v,{spec:B,item:X,className:M,emptyState:Q,headerActions:Y});case"grid":{const E=B.view,U={kind:"list",layout:"card",fields:E.fields,filters:E.filters,actions:E.actions,primaryField:E.primaryField,secondaryFields:E.secondaryFields,filterScope:E.filterScope},H={...B,view:U};return J(b,{spec:H,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q})}default:return J(f,{className:M,children:V("Unsupported data view kind",z)})}},[B,G,X,M,$,q,P,R,T,Y,Q,j,z]);if(!(B.view.kind==="list"||B.view.kind==="table"||B.view.kind==="grid"))return J(p,{children:x});return c(h,{gap:"lg",children:[(B.view.filters?.length||_||N.length)&&J(m,{searchValue:w,onSearchChange:_,searchPlaceholder:V("Search...",z)??"Search...",activeChips:N,onClearAll:k?()=>Z?.({}):void 0,right:B.view.kind==="table"?void 0:Y}),x,K&&K.total>0&&J(d,{currentPage:K.page,totalPages:Math.ceil(K.total/K.pageSize),totalItems:K.total,itemsPerPage:K.pageSize,onPageChange:(E)=>D?.(E),onItemsPerPageChange:(E)=>{D?.(1)},showItemsPerPage:!1})]})}function g(B){if(!B)return;return Object.fromEntries(Object.entries(B??{}).filter((G)=>Boolean(G[1]&&typeof G[1]==="object"&&"kind"in G[1]&&typeof G[1].kind==="string")))}function A(B,G){return B.view.filters?.find((X)=>X.key===G)?.label??G}function F(B){if(!B)return"";if(B.kind==="single")return String(B.value);if(B.kind==="multi")return B.values.map(String).join(", ");if(B.kind==="range")return[B.from==null?"":String(B.from),B.to==null?"":String(B.to)].filter(Boolean).join(" - ");return`${B.mode}(${B.clauses.length})`}
@@ -1 +1 @@
1
- import{jsx as A,jsxs as J}from"react/jsx-runtime";import{Command as k,CommandEmpty as S,CommandGroup as b,CommandInput as f,CommandItem as C,CommandList as T}from"@contractspec/lib.ui-kit-web/ui/command";import{Text as Y}from"@contractspec/lib.ui-kit-web/ui/text";import{Button as V}from"../../atoms/Button";import{HStack as w,VStack as Z}from"../../layout/Stack";import{useThemedPrimitive as v,useTranslatedText as _}from"../../primitives/themed";function M(F){return typeof F==="string"?F:String(F??"")}export function Autocomplete({query:F,options:$,selectedOptions:U,onQueryChange:E,onSelectOption:G,onRemoveOption:H,multiple:L,placeholder:P,placeholderI18n:g,readOnly:W,disabled:X,className:q,componentKey:B,themeVariant:K,emptyText:N="No results found."}){const D=_(),Q=v({defaultComponentKey:"Autocomplete",componentKey:B,themeVariant:K,className:q});return J(Z,{gap:"sm",className:Q.className,children:[J(k,{shouldFilter:!1,className:"rounded-md border border-input",children:[A(f,{value:F,onValueChange:E,placeholder:D(g??P),disabled:X||W}),J(T,{children:[A(S,{children:D(N)}),A(b,{children:$.map((z)=>{const R=U.some((I)=>M(I.value)===M(z.value));return J(C,{value:D(z.labelI18n)??z.labelI18n,onSelect:()=>G?.(z),disabled:X||z.disabled||W,children:[J(Z,{gap:"xs",children:[A(Y,{children:D(z.labelI18n)}),z.descriptionI18n?A(Y,{className:"text-muted-foreground text-xs",children:D(z.descriptionI18n)}):null]}),R?A(Y,{className:"ml-auto text-muted-foreground text-xs",children:D("Selected")}):null]},M(z.value))})})]})]}),U.length?A(w,{gap:"sm",wrap:"wrap",children:U.map((z)=>A(V,{type:"button",variant:"outline",size:"sm",onClick:()=>H?.(z),disabled:!L||W||X,children:D(z.labelI18n)},`selected-${M(z.value)}`))}):null]})}
1
+ import{jsx as v}from"react/jsx-runtime";import{Combobox as I}from"@contractspec/lib.ui-kit-web/ui/combobox";import{useThemedPrimitive as T,useTranslatedText as b}from"../../primitives/themed";function H(E){return typeof E==="string"?E:String(E??"")}function g(E,U){const F=new Set,J=[];for(const L of[...U,...E]){const M=H(L.value);if(F.has(M))continue;F.add(M);J.push(L)}return J}export function Autocomplete({query:E,options:U,selectedOptions:F,onQueryChange:J,onSelectOption:L,onRemoveOption:M,multiple:W,placeholder:X,placeholderI18n:Y,readOnly:_,disabled:j,className:q,componentKey:w,themeVariant:D,emptyText:K="No results found.",loadingText:N="Loading options...",errorText:Q="Unable to load options.",loading:R,error:Z,id:S,name:k,"aria-invalid":C,"aria-describedby":B}){const A=b(),$=T({defaultComponentKey:"Autocomplete",componentKey:w,themeVariant:D,className:q}),f=g(U,F),P=new Map(f.map((z)=>[H(z.value),z]));return v(I,{...$.props,id:S,name:k,className:$.className,options:f.map((z)=>({value:H(z.value),label:A(z.labelI18n)??z.labelI18n,description:z.descriptionI18n?A(z.descriptionI18n)??z.descriptionI18n:void 0,disabled:z.disabled})),value:W?void 0:H(F[0]?.value??""),selectedValues:W?F.map((z)=>H(z.value)):void 0,query:E,onQueryChange:J,onValueChange:(z)=>{const G=P.get(z);if(G)L?.(G)},onRemoveValue:(z)=>{const G=P.get(z);if(G)M?.(G)},multiple:W,placeholder:A(Y??X),searchPlaceholder:A(Y??X),emptyText:A(K),loadingText:A(N),errorText:A(Q),loading:R,error:Z?A(Z):null,readOnly:_,disabled:j,"aria-invalid":C,"aria-describedby":B})}
@@ -1 +1 @@
1
- import{jsx as A,jsxs as k}from"react/jsx-runtime";import{SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue,Select as Y}from"@contractspec/lib.ui-kit-web/ui/select";import{useThemedPrimitive as Z,useTranslatedText as $}from"../../primitives/themed";function B(z){return typeof z==="string"?z:String(z??"")}export function Select({options:z,value:D,onChange:F,placeholder:H,disabled:J,className:L,componentKey:M,themeVariant:O,placeholderI18n:Q,...R}){const E=$(),U=Z({defaultComponentKey:"Select",componentKey:M,themeVariant:O,className:L});return k(Y,{value:D==null?"":B(D),onValueChange:(q)=>F?.(q),disabled:J,...R,children:[A(SelectTrigger,{className:U.className,children:A(SelectValue,{placeholder:E(Q??H)})}),A(SelectContent,{children:A(SelectGroup,{children:z?.map((q,X)=>A(SelectItem,{value:B(q.value),disabled:q.disabled,children:E(q.labelI18n)??B(q.labelI18n)},`${B(q.value)}-${X}`))})})]})}export{SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue};
1
+ import{jsx as z,jsxs as M}from"react/jsx-runtime";import{SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue,Select as K}from"@contractspec/lib.ui-kit-web/ui/select";import{useThemedPrimitive as O,useTranslatedText as T}from"../../primitives/themed";import{selectGroupKey as v,selectGroupLabel as w,selectOptionGroups as G,selectOptionLabel as L,selectOptionValue as D}from"./select-options";export function Select({options:Q,groups:R,value:E,onChange:U,placeholder:X,disabled:Y,className:Z,componentKey:$,themeVariant:k,placeholderI18n:P,...W}){const B=T(),N=O({defaultComponentKey:"Select",componentKey:$,themeVariant:k,className:Z}),C=G({options:Q,groups:R});return M(K,{value:E==null?"":D(E),onValueChange:(q)=>U?.(q),disabled:Y,...W,children:[z(SelectTrigger,{className:N.className,children:z(SelectValue,{placeholder:B(P??X)})}),z(SelectContent,{children:C.map((q,F)=>{const H=v(q,F),J=w(q,B);return M(SelectGroup,{children:[J?z(SelectLabel,{children:J}):null,q.options.map((A,f)=>z(SelectItem,{value:D(A.value),disabled:A.disabled,children:L(A,B)},`${H}-${D(A.value)}-${f}`))]},`${H}-${F}`)})})]})}export{SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue};
@@ -0,0 +1 @@
1
+ export function selectOptionValue(e){return typeof e==="string"?e:String(e??"")}export function selectOptionLabel(e,t){return t(e.labelI18n)??selectOptionValue(e.labelI18n)}export function selectGroupLabel(e,t){return t(e.labelI18n??e.label)}export function selectGroupKey(e,t){return e.key??e.labelI18n??e.label??`group-${t}`}export function selectOptionGroups({options:e,groups:t}){return t?.length?t:[{options:e??[]}]}
@@ -1 +1 @@
1
- import{jsx as E,jsxs as D}from"react/jsx-runtime";import{Badge as k}from"@contractspec/lib.ui-kit-web/ui/badge";import{cn as I}from"@contractspec/lib.ui-kit-web/ui/utils";import*as K from"react";import{Button as W}from"../atoms/Button";import{Input as N}from"../atoms/Input";export function FiltersToolbar({className:X,children:Y,right:Z,searchPlaceholder:$,searchValue:G,onSearchChange:F,onSearchSubmit:L,debounceMs:O=250,activeChips:T=[],onClearAll:H}){const[J,U]=K.useState(G??"");K.useEffect(()=>{U(G??"")},[G]);K.useEffect(()=>{if(!F)return;const z=setTimeout(()=>F(J),O);return()=>clearTimeout(z)},[J,O,F]);return D("div",{className:I("space-y-2",X),children:[D("div",{className:"flex flex-col items-stretch gap-2 md:flex-row md:items-center md:justify-between",children:[D("div",{className:"flex flex-1 items-center gap-2",children:[F?D("div",{className:"flex flex-1 items-center gap-2",children:[E(N,{value:J,onChange:(z)=>U(z.target.value),onKeyDown:(z)=>{if(z.key==="Enter")L?.()},placeholder:$,keyboard:{kind:"search"}}),E(W,{variant:"outline",onPress:()=>L?.(),className:"shrink-0",children:"Rechercher"})]}):null,Y]}),Z]}),(T.length>0||H)&&D("div",{className:"flex flex-wrap items-center gap-2",children:[T.map((z)=>D(k,{variant:"secondary",className:"inline-flex items-center gap-2",children:[E("span",{children:z.label}),z.onRemove&&E("button",{type:"button","aria-label":"Supprimer le filtre",onClick:z.onRemove,className:"rounded-xs px-1 text-base hover:bg-muted",children:"×"})]},z.key)),H&&E(W,{size:"sm",variant:"ghost",onPress:H,children:"Effacer les filtres"})]})]})}
1
+ import{jsx as E,jsxs as D}from"react/jsx-runtime";import{Badge as I}from"@contractspec/lib.ui-kit-web/ui/badge";import{cn as W}from"@contractspec/lib.ui-kit-web/ui/utils";import*as K from"react";import{Button as X}from"../atoms/Button";import{Input as N}from"../atoms/Input";export function FiltersToolbar({className:Y,children:Z,right:$,searchPlaceholder:k,searchValue:G,onSearchChange:F,onSearchSubmit:L,debounceMs:O=250,activeChips:T=[],onClearAll:H}){const[J,U]=K.useState(G??"");K.useEffect(()=>{U(G??"")},[G]);K.useEffect(()=>{if(!F)return;const z=setTimeout(()=>F(J),O);return()=>clearTimeout(z)},[J,O,F]);return D("div",{className:W("space-y-2",Y),children:[D("div",{className:"flex flex-col items-stretch gap-2 md:flex-row md:items-center md:justify-between",children:[D("div",{className:"flex flex-1 items-center gap-2",children:[F?D("div",{className:"flex flex-1 items-center gap-2",children:[E(N,{value:J,onChange:(z)=>U(z.target.value),onKeyDown:(z)=>{if(z.key==="Enter")L?.()},placeholder:k,keyboard:{kind:"search"}}),E(X,{variant:"outline",onPress:()=>L?.(),className:"shrink-0",children:"Rechercher"})]}):null,Z]}),$]}),(T.length>0||H)&&D("div",{className:"flex flex-wrap items-center gap-2",children:[T.map((z)=>D(I,{variant:"secondary",className:W("inline-flex items-center gap-2",z.disabled&&"opacity-70"),children:[E("span",{children:z.label}),z.onRemove&&!z.disabled&&E("button",{type:"button","aria-label":"Supprimer le filtre",onClick:z.onRemove,className:"rounded-xs px-1 text-base hover:bg-muted",children:"×"})]},z.key)),H&&E(X,{size:"sm",variant:"ghost",onPress:H,children:"Effacer les filtres"})]})]})}
@@ -1 +1 @@
1
- import*as a from"react";export function useListUrlState({defaults:n,paramKeys:t={q:"q",page:"page",limit:"limit",sort:"sort",filters:"f"},replaceState:d=!0}){const w=a.useCallback(()=>{if(typeof window>"u")return n;const e=new URL(window.location.href).searchParams,c=(f,o)=>{const s=f?Number(f):NaN;return Number.isFinite(s)&&s>0?s:o},r=e.get(t.filters);let i=n.filters;if(r)try{i=JSON.parse(r)}catch{i=n.filters}return{q:e.get(t.q)??n.q,page:c(e.get(t.page),n.page),limit:c(e.get(t.limit),n.limit),sort:e.get(t.sort),filters:i}},[n,t]),[g,F]=a.useState(w),u=a.useCallback((l)=>{if(typeof window>"u")return;const e=new URL(window.location.href),c=e.searchParams,r={...g,...l},i=(o,s)=>{if(s==null||s===""||s==="null")c.delete(o);else c.set(o,s)};i(t.q,r.q||null);i(t.page,String(r.page));i(t.limit,String(r.limit));i(t.sort,r.sort??null);try{const o=JSON.stringify(r.filters??{});i(t.filters,o==="{}"?null:o)}catch{}const f=`${e.pathname}?${c.toString()}${e.hash}`;if(d)window.history.replaceState({},"",f);else window.history.pushState({},"",f);F(r)},[g,t,d]),S=a.useCallback((l,e)=>{u({filters:{...g.filters,[l]:e}})},[g.filters,u]),T=a.useCallback(()=>{u({filters:{},page:1})},[u]);a.useEffect(()=>{const l=()=>F(w());window.addEventListener("popstate",l);return()=>window.removeEventListener("popstate",l)},[w]);return{state:g,setState:u,setFilter:S,clearFilters:T}}
1
+ import{createInitialListFilters as N,sanitizeListUserFilters as _}from"@contractspec/lib.presentation-runtime-core";import*as Q from"react";export function useListUrlState({defaults:E,paramKeys:h={q:"q",page:"page",limit:"limit",sort:"sort",filters:"f"},replaceState:$=!0,filterScope:J}){const Y=Q.useMemo(()=>({...N(J),..._(E.filters,J)}),[E.filters,J]),Z=Q.useCallback(()=>{if(typeof window>"u")return E;const A=new URL(window.location.href).searchParams,V=(X,M)=>{const H=X?Number(X):NaN;return Number.isFinite(H)&&H>0?H:M},C=A.get(h.filters);let B=E.filters;if(C)try{B=_(JSON.parse(C),J)}catch{B=Y}else B=Y;return{q:A.get(h.q)??E.q,page:V(A.get(h.page),E.page),limit:V(A.get(h.limit),E.limit),sort:A.get(h.sort),filters:B}},[Y,E,J,h]),[T,b]=Q.useState(Z),W=Q.useCallback((G)=>{if(typeof window>"u")return;const A=new URL(window.location.href),V=A.searchParams,C={...T,...G,filters:_(G.filters??T.filters,J)},B=(M,H)=>{if(H==null||H===""||H==="null")V.delete(M);else V.set(M,H)};B(h.q,C.q||null);B(h.page,String(C.page));B(h.limit,String(C.limit));B(h.sort,C.sort??null);try{const M=JSON.stringify(C.filters??{});B(h.filters,M==="{}"?null:M)}catch{}const X=`${A.pathname}?${V.toString()}${A.hash}`;if($)window.history.replaceState({},"",X);else window.history.pushState({},"",X);b(C)},[J,T,h,$]),I=Q.useCallback((G,A)=>{W({filters:{...T.filters,[G]:A}})},[T.filters,W]),j=Q.useCallback(()=>{W({filters:{},page:1})},[W]);Q.useEffect(()=>{const G=()=>b(Z());window.addEventListener("popstate",G);return()=>window.removeEventListener("popstate",G)},[Z]);return{state:T,setState:W,setFilter:I,clearFilters:j}}
@@ -1,9 +1,9 @@
1
- const a=["light","dark"];function n(e,s){const r=Object.entries(s).sort(([t],[i])=>t.localeCompare(i)).map(([t,i])=>` ${t}: ${i};`).join(`
1
+ const o=["light","dark"],l={background:["card","popover"],foreground:["card-foreground","popover-foreground"],border:["input"]};function n(e,s){const t=Object.entries(s).sort(([r],[i])=>r.localeCompare(i)).map(([r,i])=>` ${r}: ${i};`).join(`
2
2
  `);return`${e} {
3
- ${r}
4
- }`}function l(e){return Object.assign({},...Object.values(e.modes))}function d(e){if(e.startsWith("--ds-color-"))return`--color-${e.slice(11)}`;if(e.startsWith("--ds-radius-"))return`--radius-${e.slice(12)}`;if(e.startsWith("--ds-space-"))return`--spacing-${e.slice(11)}`;if(e.startsWith("--ds-typography-"))return`--text-${e.slice(16)}`;return}function o(e){return`@theme inline {
5
- ${Object.keys(l(e)).sort().flatMap((r)=>{const t=d(r);return t?[` ${t}: var(${r});`]:[]}).join(`
3
+ ${t}
4
+ }`}function c(e){return Object.assign({},...Object.values(e.modes))}function d(e){if(e.startsWith("--ds-color-"))return`--color-${e.slice(11)}`;if(e.startsWith("--ds-radius-"))return`--radius-${e.slice(12)}`;if(e.startsWith("--ds-space-"))return`--spacing-${e.slice(11)}`;if(e.startsWith("--ds-typography-"))return`--text-${e.slice(16)}`;return}function u(e){if(!e.startsWith("--ds-color-"))return[];const s=e.slice(11);return(l[s]??[]).map((r)=>`--color-${r}`)}function h(e){return`@theme inline {
5
+ ${Object.keys(c(e)).sort().flatMap((t)=>{const r=d(t),i=u(t);return[...r?[` ${r}: var(${t});`]:[],...i.map((a)=>` ${a}: var(${t});`)]}).join(`
6
6
  `)}
7
- }`}export function themeSpecToTailwindCss(e,s={}){return[s.includeCustomVariant?"@custom-variant dark (&:where(.dark, .dark *));":void 0,n(s.rootSelector??":root",e.light),n(s.darkSelector??".dark",e.dark),...Object.entries(e.modes).filter(([t])=>!a.includes(t)).map(([t,i])=>n(`[data-theme="${t}"]`,i)),s.includeTheme===!1?void 0:o(e)].filter(Boolean).join(`
7
+ }`}export function themeSpecToTailwindCss(e,s={}){return[s.includeCustomVariant?"@custom-variant dark (&:where(.dark, .dark *));":void 0,n(s.rootSelector??":root",e.light),n(s.darkSelector??".dark",e.dark),...Object.entries(e.modes).filter(([r])=>!o.includes(r)).map(([r,i])=>n(`[data-theme="${r}"]`,i)),s.includeTheme===!1?void 0:h(e)].filter(Boolean).join(`
8
8
 
9
9
  `)}
@@ -1 +1 @@
1
- import{jsx as N}from"react/jsx-runtime";import{Input as j}from"@contractspec/lib.ui-kit-web/ui/input";import{mapKeyboardToWeb as B}from"../../lib/keyboard";import{useThemedTextField as C}from"../primitives/control";export function Input({value:A,defaultValue:D,onChange:E,onSubmit:O,onFocus:G,onBlur:H,placeholder:J,disabled:M,readOnly:P,maxLength:Q,name:R,className:S,keyboard:U,componentKey:X,themeVariant:Y,placeholderI18n:Z,ariaLabelI18n:$,...z}){const _=B(U),q=C({defaultComponentKey:"Input",componentKey:X,themeVariant:Y,className:S,style:z.style,placeholder:J,placeholderI18n:Z,ariaLabelI18n:$});return N(j,{...q.themed.props,...z,className:q.themed.className,style:q.themed.style,value:A,defaultValue:D,onChange:E,onFocus:G,onBlur:H,placeholder:q.placeholder,"aria-label":q.ariaLabel,disabled:M,readOnly:P,maxLength:Q,name:R,..._})}
1
+ import{jsx as N}from"react/jsx-runtime";import{Input as j}from"@contractspec/lib.ui-kit-web/ui/input";import{mapKeyboardToWeb as B}from"../../lib/keyboard";import{useThemedTextField as C}from"../primitives/control";export function Input({value:D,defaultValue:E,onChange:G,onSubmit:O,onFocus:H,onBlur:J,placeholder:M,disabled:P,readOnly:Q,maxLength:R,name:S,className:U,keyboard:z,componentKey:X,themeVariant:Y,placeholderI18n:Z,ariaLabelI18n:$,...A}){const _=z?B(z):{},q=C({defaultComponentKey:"Input",componentKey:X,themeVariant:Y,className:U,style:A.style,placeholder:M,placeholderI18n:Z,ariaLabelI18n:$});return N(j,{...q.themed.props,..._,...A,className:q.themed.className,style:q.themed.style,value:D,defaultValue:E,onChange:G,onFocus:H,onBlur:J,placeholder:q.placeholder,"aria-label":q.ariaLabel,disabled:P,readOnly:Q,maxLength:R,name:S})}
@@ -1,4 +1,4 @@
1
- import type { DataViewSpec } from '@contractspec/lib.contracts-spec/data-views';
1
+ import type { DataViewFilterSet, DataViewSpec } from '@contractspec/lib.contracts-spec/data-views';
2
2
  import * as React from 'react';
3
3
  export interface DataViewRendererProps {
4
4
  spec: DataViewSpec;
@@ -15,8 +15,8 @@ export interface DataViewRendererProps {
15
15
  footer?: React.ReactNode;
16
16
  search?: string;
17
17
  onSearchChange?: (value: string) => void;
18
- filters?: Record<string, unknown>;
19
- onFilterChange?: (filters: Record<string, unknown>) => void;
18
+ filters?: Record<string, unknown> | DataViewFilterSet;
19
+ onFilterChange?: (filters: Record<string, unknown> | DataViewFilterSet) => void;
20
20
  pagination?: {
21
21
  page: number;
22
22
  pageSize: number;
@@ -1 +1 @@
1
- import{jsx as G,jsxs as w,Fragment as d}from"react/jsx-runtime";import{Pagination as b}from"@contractspec/lib.ui-kit-web/ui/atoms/Pagination";import{VStack as j}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as _}from"@contractspec/lib.ui-kit-web/ui/text";import*as k from"react";import{resolveTranslationString as F,useDesignSystemTranslation as N}from"../../i18n/translation";import{FiltersToolbar as x}from"../molecules/FiltersToolbar";import{DataViewDetail as A}from"./DataViewDetail";import{DataViewList as T}from"./DataViewList";import{DataViewTable as C}from"./DataViewTable";export function DataViewRenderer({spec:E,items:M=[],item:Y=null,className:I,renderActions:Q,onSelect:U,onRowClick:Z,toolbar:$,loading:q,headerActions:O,emptyState:K,footer:z,search:V,onSearchChange:L,filters:J,onFilterChange:P,pagination:H,onPageChange:R}){const W=N(),u=k.useMemo(()=>{switch(E.view.kind){case"list":return G(T,{spec:E,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K});case"table":return G(C,{spec:E,items:M,className:I,onRowClick:Z,toolbar:$,loading:q,emptyState:K,headerActions:O,footer:z});case"detail":return G(A,{spec:E,item:Y,className:I,emptyState:K,headerActions:O});case"grid":{const B=E.view,X={kind:"list",layout:"card",fields:B.fields,filters:B.filters,actions:B.actions,primaryField:B.primaryField,secondaryFields:B.secondaryFields},v={...E,view:X};return G(T,{spec:v,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K})}default:return G(_,{className:I,children:F("Unsupported data view kind",W)})}},[E,M,Y,I,Q,U,Z,$,q,O,K,z,W]);if(!(E.view.kind==="list"||E.view.kind==="table"||E.view.kind==="grid"))return G(d,{children:u});return w(j,{gap:"lg",children:[(E.view.filters?.length||L)&&G(x,{searchValue:V,onSearchChange:L,searchPlaceholder:F("Search...",W)??"Search...",activeChips:J?Object.entries(J).map(([B,X])=>({key:B,label:`${B}: ${X}`,onRemove:()=>{if(J){const{[B]:v,...D}=J;P?.(D)}}})):[],onClearAll:J&&Object.keys(J).length>0?()=>P?.({}):void 0,right:E.view.kind==="table"?void 0:O}),u,H&&H.total>0&&G(b,{currentPage:H.page,totalPages:Math.ceil(H.total/H.pageSize),totalItems:H.total,itemsPerPage:H.pageSize,onPageChange:(B)=>R?.(B),onItemsPerPageChange:(B)=>{R?.(1)},showItemsPerPage:!1})]})}
1
+ import{jsx as J,jsxs as c,Fragment as p}from"react/jsx-runtime";import{resolveDataViewFilters as S}from"@contractspec/lib.contracts-spec/data-views";import{Pagination as d}from"@contractspec/lib.ui-kit-web/ui/atoms/Pagination";import{VStack as h}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as f}from"@contractspec/lib.ui-kit-web/ui/text";import*as L from"react";import{resolveTranslationString as V,useDesignSystemTranslation as u}from"../../i18n/translation";import{FiltersToolbar as m}from"../molecules/FiltersToolbar";import{DataViewDetail as v}from"./DataViewDetail";import{DataViewList as b}from"./DataViewList";import{DataViewTable as y}from"./DataViewTable";export function DataViewRenderer({spec:B,items:G=[],item:X=null,className:M,renderActions:$,onSelect:q,onRowClick:P,toolbar:R,loading:T,headerActions:Y,emptyState:Q,footer:j,search:w,onSearchChange:_,filters:I,onFilterChange:Z,pagination:K,onPageChange:D}){const z=u(),O=L.useMemo(()=>S({filters:B.view.filters,scope:B.view.filterScope,user:g(I)}),[I,B.view.filterScope,B.view.filters]),N=L.useMemo(()=>{if(B.view.filterScope){const E=Object.entries(O.user).map(([H,W])=>({key:H,label:`${A(B,H)}: ${F(W)}`,onRemove:()=>{const{[H]:r,...C}=O.user;Z?.(C)}})),U=O.lockedChips==="hidden"?[]:Object.entries(O.locked).map(([H,W])=>({key:`locked-${H}`,label:`${A(B,H)}: ${F(W)}`,disabled:!0}));return[...E,...U]}return I?Object.entries(I).map(([E,U])=>({key:E,label:`${E}: ${String(U)}`,onRemove:()=>{const{[E]:H,...W}=I;Z?.(W)}})):[]},[I,Z,O,B]),k=B.view.filterScope?Object.keys(O.user).length>0:Boolean(I&&Object.keys(I).length>0),x=L.useMemo(()=>{switch(B.view.kind){case"list":return J(b,{spec:B,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q});case"table":return J(y,{spec:B,items:G,className:M,onRowClick:P,toolbar:R,loading:T,emptyState:Q,headerActions:Y,footer:j});case"detail":return J(v,{spec:B,item:X,className:M,emptyState:Q,headerActions:Y});case"grid":{const E=B.view,U={kind:"list",layout:"card",fields:E.fields,filters:E.filters,actions:E.actions,primaryField:E.primaryField,secondaryFields:E.secondaryFields,filterScope:E.filterScope},H={...B,view:U};return J(b,{spec:H,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q})}default:return J(f,{className:M,children:V("Unsupported data view kind",z)})}},[B,G,X,M,$,q,P,R,T,Y,Q,j,z]);if(!(B.view.kind==="list"||B.view.kind==="table"||B.view.kind==="grid"))return J(p,{children:x});return c(h,{gap:"lg",children:[(B.view.filters?.length||_||N.length)&&J(m,{searchValue:w,onSearchChange:_,searchPlaceholder:V("Search...",z)??"Search...",activeChips:N,onClearAll:k?()=>Z?.({}):void 0,right:B.view.kind==="table"?void 0:Y}),x,K&&K.total>0&&J(d,{currentPage:K.page,totalPages:Math.ceil(K.total/K.pageSize),totalItems:K.total,itemsPerPage:K.pageSize,onPageChange:(E)=>D?.(E),onItemsPerPageChange:(E)=>{D?.(1)},showItemsPerPage:!1})]})}function g(B){if(!B)return;return Object.fromEntries(Object.entries(B??{}).filter((G)=>Boolean(G[1]&&typeof G[1]==="object"&&"kind"in G[1]&&typeof G[1].kind==="string")))}function A(B,G){return B.view.filters?.find((X)=>X.key===G)?.label??G}function F(B){if(!B)return"";if(B.kind==="single")return String(B.value);if(B.kind==="multi")return B.values.map(String).join(", ");if(B.kind==="range")return[B.from==null?"":String(B.from),B.to==null?"":String(B.to)].filter(Boolean).join(" - ");return`${B.mode}(${B.clauses.length})`}
@@ -1,4 +1,4 @@
1
- import type { DataViewSpec } from '@contractspec/lib.contracts-spec/data-views';
1
+ import type { DataViewFilterSet, DataViewSpec } from '@contractspec/lib.contracts-spec/data-views';
2
2
  import * as React from 'react';
3
3
  export interface DataViewRendererProps {
4
4
  spec: DataViewSpec;
@@ -15,8 +15,8 @@ export interface DataViewRendererProps {
15
15
  footer?: React.ReactNode;
16
16
  search?: string;
17
17
  onSearchChange?: (value: string) => void;
18
- filters?: Record<string, unknown>;
19
- onFilterChange?: (filters: Record<string, unknown>) => void;
18
+ filters?: Record<string, unknown> | DataViewFilterSet;
19
+ onFilterChange?: (filters: Record<string, unknown> | DataViewFilterSet) => void;
20
20
  pagination?: {
21
21
  page: number;
22
22
  pageSize: number;
@@ -13,5 +13,13 @@ export interface AutocompleteProps extends ThemedPrimitiveProps {
13
13
  disabled?: boolean;
14
14
  className?: string;
15
15
  emptyText?: string;
16
+ loadingText?: string;
17
+ errorText?: string;
18
+ loading?: boolean;
19
+ error?: string | null;
20
+ id?: string;
21
+ name?: string;
22
+ 'aria-invalid'?: boolean;
23
+ 'aria-describedby'?: string;
16
24
  }
17
- export declare function Autocomplete({ query, options, selectedOptions, onQueryChange, onSelectOption, onRemoveOption, multiple, placeholder, placeholderI18n, readOnly, disabled, className, componentKey, themeVariant, emptyText, }: AutocompleteProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function Autocomplete({ query, options, selectedOptions, onQueryChange, onSelectOption, onRemoveOption, multiple, placeholder, placeholderI18n, readOnly, disabled, className, componentKey, themeVariant, emptyText, loadingText, errorText, loading, error, id, name, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, }: AutocompleteProps): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- import{jsx as A,jsxs as J}from"react/jsx-runtime";import{Command as k,CommandEmpty as S,CommandGroup as b,CommandInput as f,CommandItem as C,CommandList as T}from"@contractspec/lib.ui-kit-web/ui/command";import{Text as Y}from"@contractspec/lib.ui-kit-web/ui/text";import{Button as V}from"../../atoms/Button";import{HStack as w,VStack as Z}from"../../layout/Stack";import{useThemedPrimitive as v,useTranslatedText as _}from"../../primitives/themed";function M(F){return typeof F==="string"?F:String(F??"")}export function Autocomplete({query:F,options:$,selectedOptions:U,onQueryChange:E,onSelectOption:G,onRemoveOption:H,multiple:L,placeholder:P,placeholderI18n:g,readOnly:W,disabled:X,className:q,componentKey:B,themeVariant:K,emptyText:N="No results found."}){const D=_(),Q=v({defaultComponentKey:"Autocomplete",componentKey:B,themeVariant:K,className:q});return J(Z,{gap:"sm",className:Q.className,children:[J(k,{shouldFilter:!1,className:"rounded-md border border-input",children:[A(f,{value:F,onValueChange:E,placeholder:D(g??P),disabled:X||W}),J(T,{children:[A(S,{children:D(N)}),A(b,{children:$.map((z)=>{const R=U.some((I)=>M(I.value)===M(z.value));return J(C,{value:D(z.labelI18n)??z.labelI18n,onSelect:()=>G?.(z),disabled:X||z.disabled||W,children:[J(Z,{gap:"xs",children:[A(Y,{children:D(z.labelI18n)}),z.descriptionI18n?A(Y,{className:"text-muted-foreground text-xs",children:D(z.descriptionI18n)}):null]}),R?A(Y,{className:"ml-auto text-muted-foreground text-xs",children:D("Selected")}):null]},M(z.value))})})]})]}),U.length?A(w,{gap:"sm",wrap:"wrap",children:U.map((z)=>A(V,{type:"button",variant:"outline",size:"sm",onClick:()=>H?.(z),disabled:!L||W||X,children:D(z.labelI18n)},`selected-${M(z.value)}`))}):null]})}
1
+ import{jsx as v}from"react/jsx-runtime";import{Combobox as I}from"@contractspec/lib.ui-kit-web/ui/combobox";import{useThemedPrimitive as T,useTranslatedText as b}from"../../primitives/themed";function H(E){return typeof E==="string"?E:String(E??"")}function g(E,U){const F=new Set,J=[];for(const L of[...U,...E]){const M=H(L.value);if(F.has(M))continue;F.add(M);J.push(L)}return J}export function Autocomplete({query:E,options:U,selectedOptions:F,onQueryChange:J,onSelectOption:L,onRemoveOption:M,multiple:W,placeholder:X,placeholderI18n:Y,readOnly:_,disabled:j,className:q,componentKey:w,themeVariant:D,emptyText:K="No results found.",loadingText:N="Loading options...",errorText:Q="Unable to load options.",loading:R,error:Z,id:S,name:k,"aria-invalid":C,"aria-describedby":B}){const A=b(),$=T({defaultComponentKey:"Autocomplete",componentKey:w,themeVariant:D,className:q}),f=g(U,F),P=new Map(f.map((z)=>[H(z.value),z]));return v(I,{...$.props,id:S,name:k,className:$.className,options:f.map((z)=>({value:H(z.value),label:A(z.labelI18n)??z.labelI18n,description:z.descriptionI18n?A(z.descriptionI18n)??z.descriptionI18n:void 0,disabled:z.disabled})),value:W?void 0:H(F[0]?.value??""),selectedValues:W?F.map((z)=>H(z.value)):void 0,query:E,onQueryChange:J,onValueChange:(z)=>{const G=P.get(z);if(G)L?.(G)},onRemoveValue:(z)=>{const G=P.get(z);if(G)M?.(G)},multiple:W,placeholder:A(Y??X),searchPlaceholder:A(Y??X),emptyText:A(K),loadingText:A(N),errorText:A(Q),loading:R,error:Z?A(Z):null,readOnly:_,disabled:j,"aria-invalid":C,"aria-describedby":B})}
@@ -13,5 +13,9 @@ export interface AutocompleteProps extends ThemedPrimitiveProps {
13
13
  disabled?: boolean;
14
14
  className?: string;
15
15
  emptyText?: string;
16
+ loadingText?: string;
17
+ errorText?: string;
18
+ loading?: boolean;
19
+ error?: string | null;
16
20
  }
17
- export declare function Autocomplete({ query, options, selectedOptions, onQueryChange, onSelectOption, onRemoveOption, multiple, placeholder, placeholderI18n, readOnly, disabled, className, componentKey, themeVariant, emptyText, }: AutocompleteProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function Autocomplete({ query, options, selectedOptions, onQueryChange, onSelectOption, onRemoveOption, multiple, placeholder, placeholderI18n, readOnly, disabled, className, componentKey, themeVariant, emptyText, loadingText, errorText, loading, error, }: AutocompleteProps): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +1,9 @@
1
- import type { FormOption } from '@contractspec/lib.contracts-spec/forms';
2
- import { SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@contractspec/lib.ui-kit-web/ui/select';
1
+ import { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@contractspec/lib.ui-kit-web/ui/select';
3
2
  import { type ThemedPrimitiveProps } from '../../primitives/themed';
3
+ import { type FormOption, type SelectOptionGroup } from './select-options';
4
4
  export interface SelectProps extends ThemedPrimitiveProps {
5
- options?: FormOption[];
5
+ options?: readonly FormOption[];
6
+ groups?: readonly SelectOptionGroup[];
6
7
  value?: unknown;
7
8
  onChange?: (value: unknown) => void;
8
9
  placeholder?: string;
@@ -11,5 +12,6 @@ export interface SelectProps extends ThemedPrimitiveProps {
11
12
  name?: string;
12
13
  className?: string;
13
14
  }
14
- export declare function Select({ options, value, onChange, placeholder, disabled, className, componentKey, themeVariant, placeholderI18n, ...props }: SelectProps): import("react/jsx-runtime").JSX.Element;
15
- export { SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue };
15
+ export declare function Select({ options, groups, value, onChange, placeholder, disabled, className, componentKey, themeVariant, placeholderI18n, ...props }: SelectProps): import("react/jsx-runtime").JSX.Element;
16
+ export type { SelectOptionGroup };
17
+ export { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, };
@@ -1 +1 @@
1
- import{jsx as A,jsxs as k}from"react/jsx-runtime";import{SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue,Select as Y}from"@contractspec/lib.ui-kit-web/ui/select";import{useThemedPrimitive as Z,useTranslatedText as $}from"../../primitives/themed";function B(z){return typeof z==="string"?z:String(z??"")}export function Select({options:z,value:D,onChange:F,placeholder:H,disabled:J,className:L,componentKey:M,themeVariant:O,placeholderI18n:Q,...R}){const E=$(),U=Z({defaultComponentKey:"Select",componentKey:M,themeVariant:O,className:L});return k(Y,{value:D==null?"":B(D),onValueChange:(q)=>F?.(q),disabled:J,...R,children:[A(SelectTrigger,{className:U.className,children:A(SelectValue,{placeholder:E(Q??H)})}),A(SelectContent,{children:A(SelectGroup,{children:z?.map((q,X)=>A(SelectItem,{value:B(q.value),disabled:q.disabled,children:E(q.labelI18n)??B(q.labelI18n)},`${B(q.value)}-${X}`))})})]})}export{SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue};
1
+ import{jsx as z,jsxs as M}from"react/jsx-runtime";import{SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue,Select as K}from"@contractspec/lib.ui-kit-web/ui/select";import{useThemedPrimitive as O,useTranslatedText as T}from"../../primitives/themed";import{selectGroupKey as v,selectGroupLabel as w,selectOptionGroups as G,selectOptionLabel as L,selectOptionValue as D}from"./select-options";export function Select({options:Q,groups:R,value:E,onChange:U,placeholder:X,disabled:Y,className:Z,componentKey:$,themeVariant:k,placeholderI18n:P,...W}){const B=T(),N=O({defaultComponentKey:"Select",componentKey:$,themeVariant:k,className:Z}),C=G({options:Q,groups:R});return M(K,{value:E==null?"":D(E),onValueChange:(q)=>U?.(q),disabled:Y,...W,children:[z(SelectTrigger,{className:N.className,children:z(SelectValue,{placeholder:B(P??X)})}),z(SelectContent,{children:C.map((q,F)=>{const H=v(q,F),J=w(q,B);return M(SelectGroup,{children:[J?z(SelectLabel,{children:J}):null,q.options.map((A,f)=>z(SelectItem,{value:D(A.value),disabled:A.disabled,children:L(A,B)},`${H}-${D(A.value)}-${f}`))]},`${H}-${F}`)})})]})}export{SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue};
@@ -1,13 +1,15 @@
1
- import type { FormOption } from '@contractspec/lib.contracts-spec/forms';
2
- import { SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@contractspec/lib.ui-kit/ui/select';
1
+ import { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@contractspec/lib.ui-kit/ui/select';
3
2
  import { type ThemedPrimitiveProps } from '../../primitives/themed';
3
+ import { type FormOption, type SelectOptionGroup } from './select-options';
4
4
  export interface SelectProps extends ThemedPrimitiveProps {
5
- options?: FormOption[];
5
+ options?: readonly FormOption[];
6
+ groups?: readonly SelectOptionGroup[];
6
7
  value?: unknown;
7
8
  onChange?: (value: unknown) => void;
8
9
  placeholder?: string;
9
10
  disabled?: boolean;
10
11
  className?: string;
11
12
  }
12
- export declare function Select({ options, value, onChange, placeholder, disabled, className, componentKey, themeVariant, placeholderI18n, }: SelectProps): import("react/jsx-runtime").JSX.Element;
13
- export { SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue };
13
+ export declare function Select({ options, groups, value, onChange, placeholder, disabled, className, componentKey, themeVariant, placeholderI18n, }: SelectProps): import("react/jsx-runtime").JSX.Element;
14
+ export type { SelectOptionGroup };
15
+ export { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, };
@@ -9,4 +9,4 @@ export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGr
9
9
  export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, } from './InputOTP';
10
10
  export { LoadingButton, type LoadingButtonProps } from './LoadingButton';
11
11
  export { NativeSelect, NativeSelectOptGroup, NativeSelectOption, type NativeSelectProps, } from './NativeSelect';
12
- export { Select, type SelectProps } from './Select';
12
+ export { Select, type SelectOptionGroup, type SelectProps } from './Select';
@@ -0,0 +1,17 @@
1
+ import type { FormOption } from '@contractspec/lib.contracts-spec/forms';
2
+ export type { FormOption };
3
+ export interface SelectOptionGroup {
4
+ key?: string;
5
+ label?: string;
6
+ labelI18n?: string;
7
+ options: readonly FormOption[];
8
+ }
9
+ export type SelectTextTranslator = (value: string | undefined) => string | undefined;
10
+ export declare function selectOptionValue(value: unknown): string;
11
+ export declare function selectOptionLabel(option: FormOption, translate: SelectTextTranslator): string;
12
+ export declare function selectGroupLabel(group: SelectOptionGroup, translate: SelectTextTranslator): string | undefined;
13
+ export declare function selectGroupKey(group: SelectOptionGroup, index: number): string;
14
+ export declare function selectOptionGroups({ options, groups, }: {
15
+ options?: readonly FormOption[];
16
+ groups?: readonly SelectOptionGroup[];
17
+ }): readonly SelectOptionGroup[];
@@ -0,0 +1 @@
1
+ export function selectOptionValue(e){return typeof e==="string"?e:String(e??"")}export function selectOptionLabel(e,t){return t(e.labelI18n)??selectOptionValue(e.labelI18n)}export function selectGroupLabel(e,t){return t(e.labelI18n??e.label)}export function selectGroupKey(e,t){return e.key??e.labelI18n??e.label??`group-${t}`}export function selectOptionGroups({options:e,groups:t}){return t?.length?t:[{options:e??[]}]}
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  export interface ActiveFilterChip {
3
3
  key: string;
4
4
  label: React.ReactNode;
5
+ disabled?: boolean;
5
6
  onRemove?: () => void;
6
7
  }
7
8
  export interface FiltersToolbarProps {
@@ -1 +1 @@
1
- import{jsx as E,jsxs as D}from"react/jsx-runtime";import{Badge as k}from"@contractspec/lib.ui-kit-web/ui/badge";import{cn as I}from"@contractspec/lib.ui-kit-web/ui/utils";import*as K from"react";import{Button as W}from"../atoms/Button";import{Input as N}from"../atoms/Input";export function FiltersToolbar({className:X,children:Y,right:Z,searchPlaceholder:$,searchValue:G,onSearchChange:F,onSearchSubmit:L,debounceMs:O=250,activeChips:T=[],onClearAll:H}){const[J,U]=K.useState(G??"");K.useEffect(()=>{U(G??"")},[G]);K.useEffect(()=>{if(!F)return;const z=setTimeout(()=>F(J),O);return()=>clearTimeout(z)},[J,O,F]);return D("div",{className:I("space-y-2",X),children:[D("div",{className:"flex flex-col items-stretch gap-2 md:flex-row md:items-center md:justify-between",children:[D("div",{className:"flex flex-1 items-center gap-2",children:[F?D("div",{className:"flex flex-1 items-center gap-2",children:[E(N,{value:J,onChange:(z)=>U(z.target.value),onKeyDown:(z)=>{if(z.key==="Enter")L?.()},placeholder:$,keyboard:{kind:"search"}}),E(W,{variant:"outline",onPress:()=>L?.(),className:"shrink-0",children:"Rechercher"})]}):null,Y]}),Z]}),(T.length>0||H)&&D("div",{className:"flex flex-wrap items-center gap-2",children:[T.map((z)=>D(k,{variant:"secondary",className:"inline-flex items-center gap-2",children:[E("span",{children:z.label}),z.onRemove&&E("button",{type:"button","aria-label":"Supprimer le filtre",onClick:z.onRemove,className:"rounded-xs px-1 text-base hover:bg-muted",children:"\xD7"})]},z.key)),H&&E(W,{size:"sm",variant:"ghost",onPress:H,children:"Effacer les filtres"})]})]})}
1
+ import{jsx as E,jsxs as D}from"react/jsx-runtime";import{Badge as I}from"@contractspec/lib.ui-kit-web/ui/badge";import{cn as W}from"@contractspec/lib.ui-kit-web/ui/utils";import*as K from"react";import{Button as X}from"../atoms/Button";import{Input as N}from"../atoms/Input";export function FiltersToolbar({className:Y,children:Z,right:$,searchPlaceholder:k,searchValue:G,onSearchChange:F,onSearchSubmit:L,debounceMs:O=250,activeChips:T=[],onClearAll:H}){const[J,U]=K.useState(G??"");K.useEffect(()=>{U(G??"")},[G]);K.useEffect(()=>{if(!F)return;const z=setTimeout(()=>F(J),O);return()=>clearTimeout(z)},[J,O,F]);return D("div",{className:W("space-y-2",Y),children:[D("div",{className:"flex flex-col items-stretch gap-2 md:flex-row md:items-center md:justify-between",children:[D("div",{className:"flex flex-1 items-center gap-2",children:[F?D("div",{className:"flex flex-1 items-center gap-2",children:[E(N,{value:J,onChange:(z)=>U(z.target.value),onKeyDown:(z)=>{if(z.key==="Enter")L?.()},placeholder:k,keyboard:{kind:"search"}}),E(X,{variant:"outline",onPress:()=>L?.(),className:"shrink-0",children:"Rechercher"})]}):null,Z]}),$]}),(T.length>0||H)&&D("div",{className:"flex flex-wrap items-center gap-2",children:[T.map((z)=>D(I,{variant:"secondary",className:W("inline-flex items-center gap-2",z.disabled&&"opacity-70"),children:[E("span",{children:z.label}),z.onRemove&&!z.disabled&&E("button",{type:"button","aria-label":"Supprimer le filtre",onClick:z.onRemove,className:"rounded-xs px-1 text-base hover:bg-muted",children:"\xD7"})]},z.key)),H&&E(X,{size:"sm",variant:"ghost",onPress:H,children:"Effacer les filtres"})]})]})}
@@ -1,3 +1,4 @@
1
+ import type { ListFilterScope } from '@contractspec/lib.presentation-runtime-core';
1
2
  export interface ListUrlState<TFilters extends Record<string, unknown> = Record<string, unknown>> {
2
3
  q: string;
3
4
  page: number;
@@ -5,8 +6,9 @@ export interface ListUrlState<TFilters extends Record<string, unknown> = Record<
5
6
  sort?: string | null;
6
7
  filters: TFilters;
7
8
  }
8
- export declare function useListUrlState<TFilters extends Record<string, unknown> = Record<string, unknown>>({ defaults, paramKeys, replaceState, }: {
9
+ export declare function useListUrlState<TFilters extends Record<string, unknown> = Record<string, unknown>>({ defaults, paramKeys, replaceState, filterScope, }: {
9
10
  defaults: ListUrlState<TFilters>;
11
+ filterScope?: ListFilterScope<TFilters>;
10
12
  paramKeys?: {
11
13
  q: string;
12
14
  page: string;
@@ -1 +1 @@
1
- import*as a from"react";export function useListUrlState({defaults:n,paramKeys:t={q:"q",page:"page",limit:"limit",sort:"sort",filters:"f"},replaceState:d=!0}){const w=a.useCallback(()=>{if(typeof window>"u")return n;const e=new URL(window.location.href).searchParams,c=(f,o)=>{const s=f?Number(f):NaN;return Number.isFinite(s)&&s>0?s:o},r=e.get(t.filters);let i=n.filters;if(r)try{i=JSON.parse(r)}catch{i=n.filters}return{q:e.get(t.q)??n.q,page:c(e.get(t.page),n.page),limit:c(e.get(t.limit),n.limit),sort:e.get(t.sort),filters:i}},[n,t]),[g,F]=a.useState(w),u=a.useCallback((l)=>{if(typeof window>"u")return;const e=new URL(window.location.href),c=e.searchParams,r={...g,...l},i=(o,s)=>{if(s==null||s===""||s==="null")c.delete(o);else c.set(o,s)};i(t.q,r.q||null);i(t.page,String(r.page));i(t.limit,String(r.limit));i(t.sort,r.sort??null);try{const o=JSON.stringify(r.filters??{});i(t.filters,o==="{}"?null:o)}catch{}const f=`${e.pathname}?${c.toString()}${e.hash}`;if(d)window.history.replaceState({},"",f);else window.history.pushState({},"",f);F(r)},[g,t,d]),S=a.useCallback((l,e)=>{u({filters:{...g.filters,[l]:e}})},[g.filters,u]),T=a.useCallback(()=>{u({filters:{},page:1})},[u]);a.useEffect(()=>{const l=()=>F(w());window.addEventListener("popstate",l);return()=>window.removeEventListener("popstate",l)},[w]);return{state:g,setState:u,setFilter:S,clearFilters:T}}
1
+ import{createInitialListFilters as N,sanitizeListUserFilters as _}from"@contractspec/lib.presentation-runtime-core";import*as Q from"react";export function useListUrlState({defaults:E,paramKeys:h={q:"q",page:"page",limit:"limit",sort:"sort",filters:"f"},replaceState:$=!0,filterScope:J}){const Y=Q.useMemo(()=>({...N(J),..._(E.filters,J)}),[E.filters,J]),Z=Q.useCallback(()=>{if(typeof window>"u")return E;const A=new URL(window.location.href).searchParams,V=(X,M)=>{const H=X?Number(X):NaN;return Number.isFinite(H)&&H>0?H:M},C=A.get(h.filters);let B=E.filters;if(C)try{B=_(JSON.parse(C),J)}catch{B=Y}else B=Y;return{q:A.get(h.q)??E.q,page:V(A.get(h.page),E.page),limit:V(A.get(h.limit),E.limit),sort:A.get(h.sort),filters:B}},[Y,E,J,h]),[T,b]=Q.useState(Z),W=Q.useCallback((G)=>{if(typeof window>"u")return;const A=new URL(window.location.href),V=A.searchParams,C={...T,...G,filters:_(G.filters??T.filters,J)},B=(M,H)=>{if(H==null||H===""||H==="null")V.delete(M);else V.set(M,H)};B(h.q,C.q||null);B(h.page,String(C.page));B(h.limit,String(C.limit));B(h.sort,C.sort??null);try{const M=JSON.stringify(C.filters??{});B(h.filters,M==="{}"?null:M)}catch{}const X=`${A.pathname}?${V.toString()}${A.hash}`;if($)window.history.replaceState({},"",X);else window.history.pushState({},"",X);b(C)},[J,T,h,$]),I=Q.useCallback((G,A)=>{W({filters:{...T.filters,[G]:A}})},[T.filters,W]),j=Q.useCallback(()=>{W({filters:{},page:1})},[W]);Q.useEffect(()=>{const G=()=>b(Z());window.addEventListener("popstate",G);return()=>window.removeEventListener("popstate",G)},[Z]);return{state:T,setState:W,setFilter:I,clearFilters:j}}
@@ -1 +1 @@
1
- import{jsx as N}from"react/jsx-runtime";import{Input as j}from"@contractspec/lib.ui-kit-web/ui/input";import{mapKeyboardToWeb as B}from"../../lib/keyboard";import{useThemedTextField as C}from"../primitives/control";export function Input({value:A,defaultValue:D,onChange:E,onSubmit:O,onFocus:G,onBlur:H,placeholder:J,disabled:M,readOnly:P,maxLength:Q,name:R,className:S,keyboard:U,componentKey:X,themeVariant:Y,placeholderI18n:Z,ariaLabelI18n:$,...z}){const _=B(U),q=C({defaultComponentKey:"Input",componentKey:X,themeVariant:Y,className:S,style:z.style,placeholder:J,placeholderI18n:Z,ariaLabelI18n:$});return N(j,{...q.themed.props,...z,className:q.themed.className,style:q.themed.style,value:A,defaultValue:D,onChange:E,onFocus:G,onBlur:H,placeholder:q.placeholder,"aria-label":q.ariaLabel,disabled:M,readOnly:P,maxLength:Q,name:R,..._})}
1
+ import{jsx as N}from"react/jsx-runtime";import{Input as j}from"@contractspec/lib.ui-kit-web/ui/input";import{mapKeyboardToWeb as B}from"../../lib/keyboard";import{useThemedTextField as C}from"../primitives/control";export function Input({value:D,defaultValue:E,onChange:G,onSubmit:O,onFocus:H,onBlur:J,placeholder:M,disabled:P,readOnly:Q,maxLength:R,name:S,className:U,keyboard:z,componentKey:X,themeVariant:Y,placeholderI18n:Z,ariaLabelI18n:$,...A}){const _=z?B(z):{},q=C({defaultComponentKey:"Input",componentKey:X,themeVariant:Y,className:U,style:A.style,placeholder:M,placeholderI18n:Z,ariaLabelI18n:$});return N(j,{...q.themed.props,..._,...A,className:q.themed.className,style:q.themed.style,value:D,defaultValue:E,onChange:G,onFocus:H,onBlur:J,placeholder:q.placeholder,"aria-label":q.ariaLabel,disabled:P,readOnly:Q,maxLength:R,name:S})}
@@ -1 +1 @@
1
- import{jsx as C}from"react/jsx-runtime";import{Input as _}from"@contractspec/lib.ui-kit/ui/input";import{mapKeyboardToNative as j}from"../../lib/keyboard";import{useThemedTextField as B}from"../primitives/control";export function Input({value:z,defaultValue:A,onChange:D,onSubmit:E,onFocus:G,onBlur:H,placeholder:J,disabled:M,readOnly:P,maxLength:Q,className:R,keyboard:U,componentKey:W,themeVariant:X,placeholderI18n:Y,ariaLabelI18n:Z,...$}){const w=j(U),q=B({defaultComponentKey:"Input",componentKey:W,themeVariant:X,className:R,placeholder:J,placeholderI18n:Y,ariaLabelI18n:Z});return C(_,{...q.themed.props,...$,className:q.themed.className,value:z,defaultValue:A,onChangeText:D,onSubmitEditing:E,onFocus:G,onBlur:H,placeholder:q.placeholder,accessibilityLabel:q.ariaLabel,editable:!M&&!P,maxLength:Q,...w})}export default Input;
1
+ import{jsx as C}from"react/jsx-runtime";import{Input as _}from"@contractspec/lib.ui-kit/ui/input";import{mapKeyboardToNative as j}from"../../lib/keyboard";import{useThemedTextField as B}from"../primitives/control";export function Input({value:A,defaultValue:D,onChange:E,onSubmit:G,onFocus:H,onBlur:J,placeholder:M,disabled:P,readOnly:Q,maxLength:R,className:U,keyboard:z,componentKey:W,themeVariant:X,placeholderI18n:Y,ariaLabelI18n:Z,...$}){const w=z?j(z):{},q=B({defaultComponentKey:"Input",componentKey:W,themeVariant:X,className:U,placeholder:M,placeholderI18n:Y,ariaLabelI18n:Z});return C(_,{...q.themed.props,...$,className:q.themed.className,value:A,defaultValue:D,onChangeText:E,onSubmitEditing:G,onFocus:H,onBlur:J,placeholder:q.placeholder,accessibilityLabel:q.ariaLabel,editable:!P&&!Q,maxLength:R,...w})}export default Input;
@@ -1 +1 @@
1
- import{jsx as G,jsxs as w,Fragment as d}from"react/jsx-runtime";import{Pagination as b}from"@contractspec/lib.ui-kit-web/ui/atoms/Pagination";import{VStack as j}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as _}from"@contractspec/lib.ui-kit-web/ui/text";import*as k from"react";import{resolveTranslationString as F,useDesignSystemTranslation as N}from"../../i18n/translation";import{FiltersToolbar as x}from"../molecules/FiltersToolbar";import{DataViewDetail as A}from"./DataViewDetail";import{DataViewList as T}from"./DataViewList";import{DataViewTable as C}from"./DataViewTable";export function DataViewRenderer({spec:E,items:M=[],item:Y=null,className:I,renderActions:Q,onSelect:U,onRowClick:Z,toolbar:$,loading:q,headerActions:O,emptyState:K,footer:z,search:V,onSearchChange:L,filters:J,onFilterChange:P,pagination:H,onPageChange:R}){const W=N(),u=k.useMemo(()=>{switch(E.view.kind){case"list":return G(T,{spec:E,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K});case"table":return G(C,{spec:E,items:M,className:I,onRowClick:Z,toolbar:$,loading:q,emptyState:K,headerActions:O,footer:z});case"detail":return G(A,{spec:E,item:Y,className:I,emptyState:K,headerActions:O});case"grid":{const B=E.view,X={kind:"list",layout:"card",fields:B.fields,filters:B.filters,actions:B.actions,primaryField:B.primaryField,secondaryFields:B.secondaryFields},v={...E,view:X};return G(T,{spec:v,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K})}default:return G(_,{className:I,children:F("Unsupported data view kind",W)})}},[E,M,Y,I,Q,U,Z,$,q,O,K,z,W]);if(!(E.view.kind==="list"||E.view.kind==="table"||E.view.kind==="grid"))return G(d,{children:u});return w(j,{gap:"lg",children:[(E.view.filters?.length||L)&&G(x,{searchValue:V,onSearchChange:L,searchPlaceholder:F("Search...",W)??"Search...",activeChips:J?Object.entries(J).map(([B,X])=>({key:B,label:`${B}: ${X}`,onRemove:()=>{if(J){const{[B]:v,...D}=J;P?.(D)}}})):[],onClearAll:J&&Object.keys(J).length>0?()=>P?.({}):void 0,right:E.view.kind==="table"?void 0:O}),u,H&&H.total>0&&G(b,{currentPage:H.page,totalPages:Math.ceil(H.total/H.pageSize),totalItems:H.total,itemsPerPage:H.pageSize,onPageChange:(B)=>R?.(B),onItemsPerPageChange:(B)=>{R?.(1)},showItemsPerPage:!1})]})}
1
+ import{jsx as J,jsxs as c,Fragment as p}from"react/jsx-runtime";import{resolveDataViewFilters as S}from"@contractspec/lib.contracts-spec/data-views";import{Pagination as d}from"@contractspec/lib.ui-kit-web/ui/atoms/Pagination";import{VStack as h}from"@contractspec/lib.ui-kit-web/ui/stack";import{Text as f}from"@contractspec/lib.ui-kit-web/ui/text";import*as L from"react";import{resolveTranslationString as V,useDesignSystemTranslation as u}from"../../i18n/translation";import{FiltersToolbar as m}from"../molecules/FiltersToolbar";import{DataViewDetail as v}from"./DataViewDetail";import{DataViewList as b}from"./DataViewList";import{DataViewTable as y}from"./DataViewTable";export function DataViewRenderer({spec:B,items:G=[],item:X=null,className:M,renderActions:$,onSelect:q,onRowClick:P,toolbar:R,loading:T,headerActions:Y,emptyState:Q,footer:j,search:w,onSearchChange:_,filters:I,onFilterChange:Z,pagination:K,onPageChange:D}){const z=u(),O=L.useMemo(()=>S({filters:B.view.filters,scope:B.view.filterScope,user:g(I)}),[I,B.view.filterScope,B.view.filters]),N=L.useMemo(()=>{if(B.view.filterScope){const E=Object.entries(O.user).map(([H,W])=>({key:H,label:`${A(B,H)}: ${F(W)}`,onRemove:()=>{const{[H]:r,...C}=O.user;Z?.(C)}})),U=O.lockedChips==="hidden"?[]:Object.entries(O.locked).map(([H,W])=>({key:`locked-${H}`,label:`${A(B,H)}: ${F(W)}`,disabled:!0}));return[...E,...U]}return I?Object.entries(I).map(([E,U])=>({key:E,label:`${E}: ${String(U)}`,onRemove:()=>{const{[E]:H,...W}=I;Z?.(W)}})):[]},[I,Z,O,B]),k=B.view.filterScope?Object.keys(O.user).length>0:Boolean(I&&Object.keys(I).length>0),x=L.useMemo(()=>{switch(B.view.kind){case"list":return J(b,{spec:B,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q});case"table":return J(y,{spec:B,items:G,className:M,onRowClick:P,toolbar:R,loading:T,emptyState:Q,headerActions:Y,footer:j});case"detail":return J(v,{spec:B,item:X,className:M,emptyState:Q,headerActions:Y});case"grid":{const E=B.view,U={kind:"list",layout:"card",fields:E.fields,filters:E.filters,actions:E.actions,primaryField:E.primaryField,secondaryFields:E.secondaryFields,filterScope:E.filterScope},H={...B,view:U};return J(b,{spec:H,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q})}default:return J(f,{className:M,children:V("Unsupported data view kind",z)})}},[B,G,X,M,$,q,P,R,T,Y,Q,j,z]);if(!(B.view.kind==="list"||B.view.kind==="table"||B.view.kind==="grid"))return J(p,{children:x});return c(h,{gap:"lg",children:[(B.view.filters?.length||_||N.length)&&J(m,{searchValue:w,onSearchChange:_,searchPlaceholder:V("Search...",z)??"Search...",activeChips:N,onClearAll:k?()=>Z?.({}):void 0,right:B.view.kind==="table"?void 0:Y}),x,K&&K.total>0&&J(d,{currentPage:K.page,totalPages:Math.ceil(K.total/K.pageSize),totalItems:K.total,itemsPerPage:K.pageSize,onPageChange:(E)=>D?.(E),onItemsPerPageChange:(E)=>{D?.(1)},showItemsPerPage:!1})]})}function g(B){if(!B)return;return Object.fromEntries(Object.entries(B??{}).filter((G)=>Boolean(G[1]&&typeof G[1]==="object"&&"kind"in G[1]&&typeof G[1].kind==="string")))}function A(B,G){return B.view.filters?.find((X)=>X.key===G)?.label??G}function F(B){if(!B)return"";if(B.kind==="single")return String(B.value);if(B.kind==="multi")return B.values.map(String).join(", ");if(B.kind==="range")return[B.from==null?"":String(B.from),B.to==null?"":String(B.to)].filter(Boolean).join(" - ");return`${B.mode}(${B.clauses.length})`}
@@ -1 +1 @@
1
- import{jsx as G,jsxs as d,Fragment as w}from"react/jsx-runtime";import{Pagination as D}from"@contractspec/lib.ui-kit/ui/atoms/Pagination";import{VStack as j}from"@contractspec/lib.ui-kit/ui/stack";import{Text as _}from"@contractspec/lib.ui-kit/ui/text";import*as k from"react";import{resolveTranslationString as F,useDesignSystemTranslation as N}from"../../i18n/translation";import{FiltersToolbar as x}from"../molecules/FiltersToolbar";import{DataViewDetail as A}from"./DataViewDetail";import{DataViewList as T}from"./DataViewList";import{DataViewTable as C}from"./DataViewTable";export function DataViewRenderer({spec:E,items:M=[],item:Y=null,className:I,renderActions:Q,onSelect:U,onRowClick:Z,toolbar:$,loading:q,headerActions:O,emptyState:K,footer:z,search:V,onSearchChange:L,filters:J,onFilterChange:P,pagination:H,onPageChange:R}){const W=N(),u=k.useMemo(()=>{switch(E.view.kind){case"list":return G(T,{spec:E,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K});case"table":return G(C,{spec:E,items:M,className:I,onRowClick:Z,toolbar:$,loading:q,emptyState:K,headerActions:O,footer:z});case"detail":return G(A,{spec:E,item:Y,className:I,emptyState:K,headerActions:O});case"grid":{const B=E.view,X={kind:"list",layout:"card",fields:B.fields,filters:B.filters,actions:B.actions,primaryField:B.primaryField,secondaryFields:B.secondaryFields},v={...E,view:X};return G(T,{spec:v,items:M,className:I,renderActions:Q,onSelect:U,emptyState:K})}default:return G(_,{className:I,children:F("Unsupported data view kind",W)})}},[E,M,Y,I,Q,U,Z,$,q,O,K,z,W]);if(!(E.view.kind==="list"||E.view.kind==="table"||E.view.kind==="grid"))return G(w,{children:u});return d(j,{gap:"lg",children:[(E.view.filters?.length||L)&&G(x,{searchValue:V,onSearchChange:L,searchPlaceholder:F("Search...",W)??"Search...",activeChips:J?Object.entries(J).map(([B,X])=>({key:B,label:`${B}: ${X}`,onRemove:()=>{if(J){const{[B]:v,...b}=J;P?.(b)}}})):[],onClearAll:J&&Object.keys(J).length>0?()=>P?.({}):void 0,right:E.view.kind==="table"?void 0:O}),u,H&&H.total>0?G(D,{currentPage:H.page,totalPages:Math.ceil(H.total/H.pageSize),totalItems:H.total,itemsPerPage:H.pageSize,onPageChange:(B)=>R?.(B),onItemsPerPageChange:(B)=>{R?.(1)},showItemsPerPage:!1}):null]})}
1
+ import{jsx as J,jsxs as c,Fragment as p}from"react/jsx-runtime";import{resolveDataViewFilters as S}from"@contractspec/lib.contracts-spec/data-views";import{Pagination as d}from"@contractspec/lib.ui-kit/ui/atoms/Pagination";import{VStack as h}from"@contractspec/lib.ui-kit/ui/stack";import{Text as f}from"@contractspec/lib.ui-kit/ui/text";import*as L from"react";import{resolveTranslationString as x,useDesignSystemTranslation as u}from"../../i18n/translation";import{FiltersToolbar as m}from"../molecules/FiltersToolbar";import{DataViewDetail as v}from"./DataViewDetail";import{DataViewList as V}from"./DataViewList";import{DataViewTable as y}from"./DataViewTable";export function DataViewRenderer({spec:B,items:G=[],item:X=null,className:M,renderActions:$,onSelect:q,onRowClick:P,toolbar:R,loading:T,headerActions:Y,emptyState:Q,footer:j,search:w,onSearchChange:_,filters:I,onFilterChange:Z,pagination:K,onPageChange:D}){const z=u(),O=L.useMemo(()=>S({filters:B.view.filters,scope:B.view.filterScope,user:g(I)}),[I,B.view.filterScope,B.view.filters]),N=L.useMemo(()=>{if(B.view.filterScope){const E=Object.entries(O.user).map(([H,W])=>({key:H,label:`${A(B,H)}: ${F(W)}`,onRemove:()=>{const{[H]:r,...C}=O.user;Z?.(C)}})),U=O.lockedChips==="hidden"?[]:Object.entries(O.locked).map(([H,W])=>({key:`locked-${H}`,label:`${A(B,H)}: ${F(W)}`,disabled:!0}));return[...E,...U]}return I?Object.entries(I).map(([E,U])=>({key:E,label:`${E}: ${String(U)}`,onRemove:()=>{const{[E]:H,...W}=I;Z?.(W)}})):[]},[I,Z,O,B]),k=B.view.filterScope?Object.keys(O.user).length>0:Boolean(I&&Object.keys(I).length>0),b=L.useMemo(()=>{switch(B.view.kind){case"list":return J(V,{spec:B,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q});case"table":return J(y,{spec:B,items:G,className:M,onRowClick:P,toolbar:R,loading:T,emptyState:Q,headerActions:Y,footer:j});case"detail":return J(v,{spec:B,item:X,className:M,emptyState:Q,headerActions:Y});case"grid":{const E=B.view,U={kind:"list",layout:"card",fields:E.fields,filters:E.filters,actions:E.actions,primaryField:E.primaryField,secondaryFields:E.secondaryFields,filterScope:E.filterScope},H={...B,view:U};return J(V,{spec:H,items:G,className:M,renderActions:$,onSelect:q,emptyState:Q})}default:return J(f,{className:M,children:x("Unsupported data view kind",z)})}},[B,G,X,M,$,q,P,R,T,Y,Q,j,z]);if(!(B.view.kind==="list"||B.view.kind==="table"||B.view.kind==="grid"))return J(p,{children:b});return c(h,{gap:"lg",children:[(B.view.filters?.length||_||N.length)&&J(m,{searchValue:w,onSearchChange:_,searchPlaceholder:x("Search...",z)??"Search...",activeChips:N,onClearAll:k?()=>Z?.({}):void 0,right:B.view.kind==="table"?void 0:Y}),b,K&&K.total>0?J(d,{currentPage:K.page,totalPages:Math.ceil(K.total/K.pageSize),totalItems:K.total,itemsPerPage:K.pageSize,onPageChange:(E)=>D?.(E),onItemsPerPageChange:(E)=>{D?.(1)},showItemsPerPage:!1}):null]})}function g(B){if(!B)return;return Object.fromEntries(Object.entries(B??{}).filter((G)=>Boolean(G[1]&&typeof G[1]==="object"&&"kind"in G[1]&&typeof G[1].kind==="string")))}function A(B,G){return B.view.filters?.find((X)=>X.key===G)?.label??G}function F(B){if(!B)return"";if(B.kind==="single")return String(B.value);if(B.kind==="multi")return B.values.map(String).join(", ");if(B.kind==="range")return[B.from==null?"":String(B.from),B.to==null?"":String(B.to)].filter(Boolean).join(" - ");return`${B.mode}(${B.clauses.length})`}
@@ -1 +1 @@
1
- import{jsx as A,jsxs as J}from"react/jsx-runtime";import{Command as k,CommandEmpty as S,CommandGroup as b,CommandInput as f,CommandItem as C,CommandList as T}from"@contractspec/lib.ui-kit-web/ui/command";import{Text as Y}from"@contractspec/lib.ui-kit-web/ui/text";import{Button as V}from"../../atoms/Button";import{HStack as w,VStack as Z}from"../../layout/Stack";import{useThemedPrimitive as v,useTranslatedText as _}from"../../primitives/themed";function M(F){return typeof F==="string"?F:String(F??"")}export function Autocomplete({query:F,options:$,selectedOptions:U,onQueryChange:E,onSelectOption:G,onRemoveOption:H,multiple:L,placeholder:P,placeholderI18n:g,readOnly:W,disabled:X,className:q,componentKey:B,themeVariant:K,emptyText:N="No results found."}){const D=_(),Q=v({defaultComponentKey:"Autocomplete",componentKey:B,themeVariant:K,className:q});return J(Z,{gap:"sm",className:Q.className,children:[J(k,{shouldFilter:!1,className:"rounded-md border border-input",children:[A(f,{value:F,onValueChange:E,placeholder:D(g??P),disabled:X||W}),J(T,{children:[A(S,{children:D(N)}),A(b,{children:$.map((z)=>{const R=U.some((I)=>M(I.value)===M(z.value));return J(C,{value:D(z.labelI18n)??z.labelI18n,onSelect:()=>G?.(z),disabled:X||z.disabled||W,children:[J(Z,{gap:"xs",children:[A(Y,{children:D(z.labelI18n)}),z.descriptionI18n?A(Y,{className:"text-muted-foreground text-xs",children:D(z.descriptionI18n)}):null]}),R?A(Y,{className:"ml-auto text-muted-foreground text-xs",children:D("Selected")}):null]},M(z.value))})})]})]}),U.length?A(w,{gap:"sm",wrap:"wrap",children:U.map((z)=>A(V,{type:"button",variant:"outline",size:"sm",onClick:()=>H?.(z),disabled:!L||W||X,children:D(z.labelI18n)},`selected-${M(z.value)}`))}):null]})}
1
+ import{jsx as v}from"react/jsx-runtime";import{Combobox as I}from"@contractspec/lib.ui-kit-web/ui/combobox";import{useThemedPrimitive as T,useTranslatedText as b}from"../../primitives/themed";function H(E){return typeof E==="string"?E:String(E??"")}function g(E,U){const F=new Set,J=[];for(const L of[...U,...E]){const M=H(L.value);if(F.has(M))continue;F.add(M);J.push(L)}return J}export function Autocomplete({query:E,options:U,selectedOptions:F,onQueryChange:J,onSelectOption:L,onRemoveOption:M,multiple:W,placeholder:X,placeholderI18n:Y,readOnly:_,disabled:j,className:q,componentKey:w,themeVariant:D,emptyText:K="No results found.",loadingText:N="Loading options...",errorText:Q="Unable to load options.",loading:R,error:Z,id:S,name:k,"aria-invalid":C,"aria-describedby":B}){const A=b(),$=T({defaultComponentKey:"Autocomplete",componentKey:w,themeVariant:D,className:q}),f=g(U,F),P=new Map(f.map((z)=>[H(z.value),z]));return v(I,{...$.props,id:S,name:k,className:$.className,options:f.map((z)=>({value:H(z.value),label:A(z.labelI18n)??z.labelI18n,description:z.descriptionI18n?A(z.descriptionI18n)??z.descriptionI18n:void 0,disabled:z.disabled})),value:W?void 0:H(F[0]?.value??""),selectedValues:W?F.map((z)=>H(z.value)):void 0,query:E,onQueryChange:J,onValueChange:(z)=>{const G=P.get(z);if(G)L?.(G)},onRemoveValue:(z)=>{const G=P.get(z);if(G)M?.(G)},multiple:W,placeholder:A(Y??X),searchPlaceholder:A(Y??X),emptyText:A(K),loadingText:A(N),errorText:A(Q),loading:R,error:Z?A(Z):null,readOnly:_,disabled:j,"aria-invalid":C,"aria-describedby":B})}
@@ -1 +1 @@
1
- import{jsx as A,jsxs as S}from"react/jsx-runtime";import{Text as J}from"@contractspec/lib.ui-kit/ui/text";import{Button as U}from"../../atoms/Button";import{Input as R}from"../../atoms/Input";import{HStack as B,VStack as W}from"../../layout/Stack";import{useThemedPrimitive as I,useTranslatedText as k}from"../../primitives/themed";function X(D){return typeof D==="string"?D:String(D??"")}export function Autocomplete({query:D,options:L,selectedOptions:M,onQueryChange:Y,onSelectOption:Z,onRemoveOption:$,multiple:H,placeholder:P,placeholderI18n:g,readOnly:F,disabled:G,className:q,componentKey:C,themeVariant:K,emptyText:N="No results found."}){const E=k(),Q=I({defaultComponentKey:"Autocomplete",componentKey:C,themeVariant:K,className:q});return S(W,{gap:"sm",className:Q.className,children:[A(R,{value:D,onChange:(z)=>{if(typeof z==="string")Y?.(z)},placeholder:E(g??P),disabled:G||F}),A(W,{gap:"xs",children:L.length?L.map((z)=>A(U,{variant:"ghost",onPress:()=>Z?.(z),disabled:G||z.disabled||F,children:A(J,{children:E(z.labelI18n)})},X(z.value))):A(J,{children:E(N)})}),M.length?A(B,{gap:"sm",wrap:"wrap",children:M.map((z)=>A(U,{variant:"outline",size:"sm",onPress:()=>$?.(z),disabled:!H||F||G,children:A(J,{children:E(z.labelI18n)})},`selected-${X(z.value)}`))}):null]})}
1
+ import{jsx as A,jsxs as w}from"react/jsx-runtime";import{Text as F}from"@contractspec/lib.ui-kit/ui/text";import{Button as W}from"../../atoms/Button";import{Input as S}from"../../atoms/Input";import{HStack as V,VStack as X}from"../../layout/Stack";import{useThemedPrimitive as b,useTranslatedText as f}from"../../primitives/themed";function Y(E){return typeof E==="string"?E:String(E??"")}export function Autocomplete({query:E,options:L,selectedOptions:M,onQueryChange:Z,onSelectOption:$,onRemoveOption:H,multiple:P,placeholder:q,placeholderI18n:C,readOnly:G,disabled:J,className:K,componentKey:N,themeVariant:Q,emptyText:R="No results found.",loadingText:B="Loading options...",errorText:I="Unable to load options.",loading:k,error:U}){const D=f(),g=b({defaultComponentKey:"Autocomplete",componentKey:N,themeVariant:Q,className:K});return w(X,{gap:"sm",className:g.className,children:[A(S,{value:E,onChange:(z)=>{if(typeof z==="string")Z?.(z)},placeholder:D(C??q),disabled:J||G}),A(X,{gap:"xs",children:k?A(F,{children:D(B)}):U?A(F,{children:D(U)??D(I)}):L.length?L.map((z)=>A(W,{variant:"ghost",onPress:()=>$?.(z),disabled:J||z.disabled||G,children:A(F,{children:D(z.labelI18n)})},Y(z.value))):A(F,{children:D(R)})}),M.length?A(V,{gap:"sm",wrap:"wrap",children:M.map((z)=>A(W,{variant:"outline",size:"sm",onPress:()=>H?.(z),disabled:!P||G||J,children:A(F,{children:D(z.labelI18n)})},`selected-${Y(z.value)}`))}):null]})}
@@ -1 +1 @@
1
- import{jsx as A,jsxs as k}from"react/jsx-runtime";import{SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue,Select as Y}from"@contractspec/lib.ui-kit-web/ui/select";import{useThemedPrimitive as Z,useTranslatedText as $}from"../../primitives/themed";function B(z){return typeof z==="string"?z:String(z??"")}export function Select({options:z,value:D,onChange:F,placeholder:H,disabled:J,className:L,componentKey:M,themeVariant:O,placeholderI18n:Q,...R}){const E=$(),U=Z({defaultComponentKey:"Select",componentKey:M,themeVariant:O,className:L});return k(Y,{value:D==null?"":B(D),onValueChange:(q)=>F?.(q),disabled:J,...R,children:[A(SelectTrigger,{className:U.className,children:A(SelectValue,{placeholder:E(Q??H)})}),A(SelectContent,{children:A(SelectGroup,{children:z?.map((q,X)=>A(SelectItem,{value:B(q.value),disabled:q.disabled,children:E(q.labelI18n)??B(q.labelI18n)},`${B(q.value)}-${X}`))})})]})}export{SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue};
1
+ import{jsx as z,jsxs as M}from"react/jsx-runtime";import{SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue,Select as K}from"@contractspec/lib.ui-kit-web/ui/select";import{useThemedPrimitive as O,useTranslatedText as T}from"../../primitives/themed";import{selectGroupKey as v,selectGroupLabel as w,selectOptionGroups as G,selectOptionLabel as L,selectOptionValue as D}from"./select-options";export function Select({options:Q,groups:R,value:E,onChange:U,placeholder:X,disabled:Y,className:Z,componentKey:$,themeVariant:k,placeholderI18n:P,...W}){const B=T(),N=O({defaultComponentKey:"Select",componentKey:$,themeVariant:k,className:Z}),C=G({options:Q,groups:R});return M(K,{value:E==null?"":D(E),onValueChange:(q)=>U?.(q),disabled:Y,...W,children:[z(SelectTrigger,{className:N.className,children:z(SelectValue,{placeholder:B(P??X)})}),z(SelectContent,{children:C.map((q,F)=>{const H=v(q,F),J=w(q,B);return M(SelectGroup,{children:[J?z(SelectLabel,{children:J}):null,q.options.map((A,f)=>z(SelectItem,{value:D(A.value),disabled:A.disabled,children:L(A,B)},`${H}-${D(A.value)}-${f}`))]},`${H}-${F}`)})})]})}export{SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue};
@@ -1 +1 @@
1
- import{jsx as B,jsxs as $}from"react/jsx-runtime";import{Select as X,SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue}from"@contractspec/lib.ui-kit/ui/select";import{useThemedPrimitive as Y,useTranslatedText as Z}from"../../primitives/themed";function A(z){return typeof z==="string"?z:String(z??"")}export function Select({options:z,value:D,onChange:F,placeholder:H,disabled:J,className:L,componentKey:M,themeVariant:O,placeholderI18n:Q}){const E=Z(),U=Y({defaultComponentKey:"Select",componentKey:M,themeVariant:O,className:L});return $(X,{value:D==null?void 0:{value:A(D),label:A(D)},onValueChange:(q)=>F?.(q?.value),children:[B(SelectTrigger,{disabled:J,className:U.className,children:B(SelectValue,{placeholder:E(Q??H)??""})}),B(SelectContent,{children:B(SelectGroup,{children:z?.map((q,W)=>B(SelectItem,{value:A(q.value),label:E(q.labelI18n)??A(q.labelI18n),disabled:q.disabled},`${A(q.value)}-${W}`))})})]})}export{SelectContent,SelectGroup,SelectItem,SelectTrigger,SelectValue};
1
+ import{jsx as B,jsxs as X}from"react/jsx-runtime";import{Select as v,SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue}from"@contractspec/lib.ui-kit/ui/select";import{useThemedPrimitive as G,useTranslatedText as _}from"../../primitives/themed";import{selectGroupKey as L,selectGroupLabel as j,selectOptionGroups as y,selectOptionLabel as W,selectOptionValue as A}from"./select-options";export function Select({options:Y,groups:Z,value:H,onChange:$,placeholder:k,disabled:P,className:R,componentKey:N,themeVariant:C,placeholderI18n:f}){const D=_(),w=G({defaultComponentKey:"Select",componentKey:N,themeVariant:C,className:R}),J=y({options:Y,groups:Z}),z=H==null?void 0:A(H),F=J.flatMap((q)=>q.options).find((q)=>A(q.value)===z),K=F?W(F,D)??A(F.value):z;return X(v,{value:z==null?void 0:{value:z,label:K??z},onValueChange:(q)=>$?.(q?.value),children:[B(SelectTrigger,{disabled:P,className:w.className,children:B(SelectValue,{placeholder:D(f??k)??""})}),B(SelectContent,{children:J.map((q,M)=>{const Q=L(q,M),U=j(q,D);return X(SelectGroup,{children:[U?B(SelectLabel,{children:U}):null,q.options.map((E,T)=>B(SelectItem,{value:A(E.value),label:W(E,D),disabled:E.disabled},`${Q}-${A(E.value)}-${T}`))]},`${Q}-${M}`)})})]})}export{SelectContent,SelectGroup,SelectItem,SelectLabel,SelectTrigger,SelectValue};
@@ -0,0 +1 @@
1
+ export function selectOptionValue(e){return typeof e==="string"?e:String(e??"")}export function selectOptionLabel(e,t){return t(e.labelI18n)??selectOptionValue(e.labelI18n)}export function selectGroupLabel(e,t){return t(e.labelI18n??e.label)}export function selectGroupKey(e,t){return e.key??e.labelI18n??e.label??`group-${t}`}export function selectOptionGroups({options:e,groups:t}){return t?.length?t:[{options:e??[]}]}
@@ -1 +1 @@
1
- import{jsx as E,jsxs as D}from"react/jsx-runtime";import{Badge as k}from"@contractspec/lib.ui-kit-web/ui/badge";import{cn as I}from"@contractspec/lib.ui-kit-web/ui/utils";import*as K from"react";import{Button as W}from"../atoms/Button";import{Input as N}from"../atoms/Input";export function FiltersToolbar({className:X,children:Y,right:Z,searchPlaceholder:$,searchValue:G,onSearchChange:F,onSearchSubmit:L,debounceMs:O=250,activeChips:T=[],onClearAll:H}){const[J,U]=K.useState(G??"");K.useEffect(()=>{U(G??"")},[G]);K.useEffect(()=>{if(!F)return;const z=setTimeout(()=>F(J),O);return()=>clearTimeout(z)},[J,O,F]);return D("div",{className:I("space-y-2",X),children:[D("div",{className:"flex flex-col items-stretch gap-2 md:flex-row md:items-center md:justify-between",children:[D("div",{className:"flex flex-1 items-center gap-2",children:[F?D("div",{className:"flex flex-1 items-center gap-2",children:[E(N,{value:J,onChange:(z)=>U(z.target.value),onKeyDown:(z)=>{if(z.key==="Enter")L?.()},placeholder:$,keyboard:{kind:"search"}}),E(W,{variant:"outline",onPress:()=>L?.(),className:"shrink-0",children:"Rechercher"})]}):null,Y]}),Z]}),(T.length>0||H)&&D("div",{className:"flex flex-wrap items-center gap-2",children:[T.map((z)=>D(k,{variant:"secondary",className:"inline-flex items-center gap-2",children:[E("span",{children:z.label}),z.onRemove&&E("button",{type:"button","aria-label":"Supprimer le filtre",onClick:z.onRemove,className:"rounded-xs px-1 text-base hover:bg-muted",children:"×"})]},z.key)),H&&E(W,{size:"sm",variant:"ghost",onPress:H,children:"Effacer les filtres"})]})]})}
1
+ import{jsx as E,jsxs as D}from"react/jsx-runtime";import{Badge as I}from"@contractspec/lib.ui-kit-web/ui/badge";import{cn as W}from"@contractspec/lib.ui-kit-web/ui/utils";import*as K from"react";import{Button as X}from"../atoms/Button";import{Input as N}from"../atoms/Input";export function FiltersToolbar({className:Y,children:Z,right:$,searchPlaceholder:k,searchValue:G,onSearchChange:F,onSearchSubmit:L,debounceMs:O=250,activeChips:T=[],onClearAll:H}){const[J,U]=K.useState(G??"");K.useEffect(()=>{U(G??"")},[G]);K.useEffect(()=>{if(!F)return;const z=setTimeout(()=>F(J),O);return()=>clearTimeout(z)},[J,O,F]);return D("div",{className:W("space-y-2",Y),children:[D("div",{className:"flex flex-col items-stretch gap-2 md:flex-row md:items-center md:justify-between",children:[D("div",{className:"flex flex-1 items-center gap-2",children:[F?D("div",{className:"flex flex-1 items-center gap-2",children:[E(N,{value:J,onChange:(z)=>U(z.target.value),onKeyDown:(z)=>{if(z.key==="Enter")L?.()},placeholder:k,keyboard:{kind:"search"}}),E(X,{variant:"outline",onPress:()=>L?.(),className:"shrink-0",children:"Rechercher"})]}):null,Z]}),$]}),(T.length>0||H)&&D("div",{className:"flex flex-wrap items-center gap-2",children:[T.map((z)=>D(I,{variant:"secondary",className:W("inline-flex items-center gap-2",z.disabled&&"opacity-70"),children:[E("span",{children:z.label}),z.onRemove&&!z.disabled&&E("button",{type:"button","aria-label":"Supprimer le filtre",onClick:z.onRemove,className:"rounded-xs px-1 text-base hover:bg-muted",children:"×"})]},z.key)),H&&E(X,{size:"sm",variant:"ghost",onPress:H,children:"Effacer les filtres"})]})]})}
@@ -1 +1 @@
1
- import{jsx as E,jsxs as F}from"react/jsx-runtime";import{HStack as L,VStack as P}from"@contractspec/lib.ui-kit/ui/stack";import*as O from"react";import{Button as T}from"../atoms/Button";import{Input as Q}from"../atoms/Input";export function FiltersToolbar({className:Y,children:Z,right:$,searchPlaceholder:I,searchValue:G,onSearchChange:D,onSearchSubmit:N,debounceMs:U=250,activeChips:W=[],onClearAll:J}){const[K,X]=O.useState(G??"");O.useEffect(()=>{X(G??"")},[G]);O.useEffect(()=>{if(!D)return;const z=setTimeout(()=>D(K),U);return()=>clearTimeout(z)},[K,U,D]);return F(P,{className:Y,children:[F(L,{className:"items-center gap-2",children:[D?F(L,{className:"flex-1 items-center gap-2",children:[E(Q,{value:K,onChange:(z)=>X(z.target.value),placeholder:I,keyboard:{kind:"search"}}),E(T,{variant:"outline",onPress:()=>N?.(),children:"Rechercher"})]}):null,Z,$]}),(W.length>0||J)&&F(L,{className:"mt-2 flex flex-wrap items-center gap-2",children:[W.map((z)=>E(T,{size:"sm",variant:"secondary",onPress:z.onRemove,children:z.label},z.key)),J?E(T,{size:"sm",variant:"ghost",onPress:J,children:"Effacer les filtres"}):null]})]})}
1
+ import{jsx as E,jsxs as F}from"react/jsx-runtime";import{HStack as L,VStack as P}from"@contractspec/lib.ui-kit/ui/stack";import*as O from"react";import{Button as T}from"../atoms/Button";import{Input as Q}from"../atoms/Input";export function FiltersToolbar({className:Y,children:Z,right:$,searchPlaceholder:I,searchValue:G,onSearchChange:D,onSearchSubmit:N,debounceMs:U=250,activeChips:W=[],onClearAll:J}){const[K,X]=O.useState(G??"");O.useEffect(()=>{X(G??"")},[G]);O.useEffect(()=>{if(!D)return;const z=setTimeout(()=>D(K),U);return()=>clearTimeout(z)},[K,U,D]);return F(P,{className:Y,children:[F(L,{className:"items-center gap-2",children:[D?F(L,{className:"flex-1 items-center gap-2",children:[E(Q,{value:K,onChange:(z)=>X(z.target.value),placeholder:I,keyboard:{kind:"search"}}),E(T,{variant:"outline",onPress:()=>N?.(),children:"Rechercher"})]}):null,Z,$]}),(W.length>0||J)&&F(L,{className:"mt-2 flex flex-wrap items-center gap-2",children:[W.map((z)=>E(T,{size:"sm",variant:"secondary",disabled:z.disabled,onPress:z.disabled?void 0:z.onRemove,children:z.label},z.key)),J?E(T,{size:"sm",variant:"ghost",onPress:J,children:"Effacer les filtres"}):null]})]})}
@@ -1 +1 @@
1
- import*as a from"react";export function useListUrlState({defaults:n,paramKeys:t={q:"q",page:"page",limit:"limit",sort:"sort",filters:"f"},replaceState:d=!0}){const w=a.useCallback(()=>{if(typeof window>"u")return n;const e=new URL(window.location.href).searchParams,c=(f,o)=>{const s=f?Number(f):NaN;return Number.isFinite(s)&&s>0?s:o},r=e.get(t.filters);let i=n.filters;if(r)try{i=JSON.parse(r)}catch{i=n.filters}return{q:e.get(t.q)??n.q,page:c(e.get(t.page),n.page),limit:c(e.get(t.limit),n.limit),sort:e.get(t.sort),filters:i}},[n,t]),[g,F]=a.useState(w),u=a.useCallback((l)=>{if(typeof window>"u")return;const e=new URL(window.location.href),c=e.searchParams,r={...g,...l},i=(o,s)=>{if(s==null||s===""||s==="null")c.delete(o);else c.set(o,s)};i(t.q,r.q||null);i(t.page,String(r.page));i(t.limit,String(r.limit));i(t.sort,r.sort??null);try{const o=JSON.stringify(r.filters??{});i(t.filters,o==="{}"?null:o)}catch{}const f=`${e.pathname}?${c.toString()}${e.hash}`;if(d)window.history.replaceState({},"",f);else window.history.pushState({},"",f);F(r)},[g,t,d]),S=a.useCallback((l,e)=>{u({filters:{...g.filters,[l]:e}})},[g.filters,u]),T=a.useCallback(()=>{u({filters:{},page:1})},[u]);a.useEffect(()=>{const l=()=>F(w());window.addEventListener("popstate",l);return()=>window.removeEventListener("popstate",l)},[w]);return{state:g,setState:u,setFilter:S,clearFilters:T}}
1
+ import{createInitialListFilters as N,sanitizeListUserFilters as _}from"@contractspec/lib.presentation-runtime-core";import*as Q from"react";export function useListUrlState({defaults:E,paramKeys:h={q:"q",page:"page",limit:"limit",sort:"sort",filters:"f"},replaceState:$=!0,filterScope:J}){const Y=Q.useMemo(()=>({...N(J),..._(E.filters,J)}),[E.filters,J]),Z=Q.useCallback(()=>{if(typeof window>"u")return E;const A=new URL(window.location.href).searchParams,V=(X,M)=>{const H=X?Number(X):NaN;return Number.isFinite(H)&&H>0?H:M},C=A.get(h.filters);let B=E.filters;if(C)try{B=_(JSON.parse(C),J)}catch{B=Y}else B=Y;return{q:A.get(h.q)??E.q,page:V(A.get(h.page),E.page),limit:V(A.get(h.limit),E.limit),sort:A.get(h.sort),filters:B}},[Y,E,J,h]),[T,b]=Q.useState(Z),W=Q.useCallback((G)=>{if(typeof window>"u")return;const A=new URL(window.location.href),V=A.searchParams,C={...T,...G,filters:_(G.filters??T.filters,J)},B=(M,H)=>{if(H==null||H===""||H==="null")V.delete(M);else V.set(M,H)};B(h.q,C.q||null);B(h.page,String(C.page));B(h.limit,String(C.limit));B(h.sort,C.sort??null);try{const M=JSON.stringify(C.filters??{});B(h.filters,M==="{}"?null:M)}catch{}const X=`${A.pathname}?${V.toString()}${A.hash}`;if($)window.history.replaceState({},"",X);else window.history.pushState({},"",X);b(C)},[J,T,h,$]),I=Q.useCallback((G,A)=>{W({filters:{...T.filters,[G]:A}})},[T.filters,W]),j=Q.useCallback(()=>{W({filters:{},page:1})},[W]);Q.useEffect(()=>{const G=()=>b(Z());window.addEventListener("popstate",G);return()=>window.removeEventListener("popstate",G)},[Z]);return{state:T,setState:W,setFilter:I,clearFilters:j}}
@@ -1,9 +1,9 @@
1
- const a=["light","dark"];function n(e,s){const r=Object.entries(s).sort(([t],[i])=>t.localeCompare(i)).map(([t,i])=>` ${t}: ${i};`).join(`
1
+ const o=["light","dark"],l={background:["card","popover"],foreground:["card-foreground","popover-foreground"],border:["input"]};function n(e,s){const t=Object.entries(s).sort(([r],[i])=>r.localeCompare(i)).map(([r,i])=>` ${r}: ${i};`).join(`
2
2
  `);return`${e} {
3
- ${r}
4
- }`}function l(e){return Object.assign({},...Object.values(e.modes))}function d(e){if(e.startsWith("--ds-color-"))return`--color-${e.slice(11)}`;if(e.startsWith("--ds-radius-"))return`--radius-${e.slice(12)}`;if(e.startsWith("--ds-space-"))return`--spacing-${e.slice(11)}`;if(e.startsWith("--ds-typography-"))return`--text-${e.slice(16)}`;return}function o(e){return`@theme inline {
5
- ${Object.keys(l(e)).sort().flatMap((r)=>{const t=d(r);return t?[` ${t}: var(${r});`]:[]}).join(`
3
+ ${t}
4
+ }`}function c(e){return Object.assign({},...Object.values(e.modes))}function d(e){if(e.startsWith("--ds-color-"))return`--color-${e.slice(11)}`;if(e.startsWith("--ds-radius-"))return`--radius-${e.slice(12)}`;if(e.startsWith("--ds-space-"))return`--spacing-${e.slice(11)}`;if(e.startsWith("--ds-typography-"))return`--text-${e.slice(16)}`;return}function u(e){if(!e.startsWith("--ds-color-"))return[];const s=e.slice(11);return(l[s]??[]).map((r)=>`--color-${r}`)}function h(e){return`@theme inline {
5
+ ${Object.keys(c(e)).sort().flatMap((t)=>{const r=d(t),i=u(t);return[...r?[` ${r}: var(${t});`]:[],...i.map((a)=>` ${a}: var(${t});`)]}).join(`
6
6
  `)}
7
- }`}export function themeSpecToTailwindCss(e,s={}){return[s.includeCustomVariant?"@custom-variant dark (&:where(.dark, .dark *));":void 0,n(s.rootSelector??":root",e.light),n(s.darkSelector??".dark",e.dark),...Object.entries(e.modes).filter(([t])=>!a.includes(t)).map(([t,i])=>n(`[data-theme="${t}"]`,i)),s.includeTheme===!1?void 0:o(e)].filter(Boolean).join(`
7
+ }`}export function themeSpecToTailwindCss(e,s={}){return[s.includeCustomVariant?"@custom-variant dark (&:where(.dark, .dark *));":void 0,n(s.rootSelector??":root",e.light),n(s.darkSelector??".dark",e.dark),...Object.entries(e.modes).filter(([r])=>!o.includes(r)).map(([r,i])=>n(`[data-theme="${r}"]`,i)),s.includeTheme===!1?void 0:h(e)].filter(Boolean).join(`
8
8
 
9
9
  `)}
@@ -1,9 +1,9 @@
1
- const a=["light","dark"];function n(e,s){const r=Object.entries(s).sort(([t],[i])=>t.localeCompare(i)).map(([t,i])=>` ${t}: ${i};`).join(`
1
+ const o=["light","dark"];const l={background:["card","popover"],foreground:["card-foreground","popover-foreground"],border:["input"]};function n(e,s){const t=Object.entries(s).sort(([r],[i])=>r.localeCompare(i)).map(([r,i])=>` ${r}: ${i};`).join(`
2
2
  `);return`${e} {
3
- ${r}
4
- }`}function l(e){return Object.assign({},...Object.values(e.modes))}function d(e){if(e.startsWith("--ds-color-"))return`--color-${e.slice(11)}`;if(e.startsWith("--ds-radius-"))return`--radius-${e.slice(12)}`;if(e.startsWith("--ds-space-"))return`--spacing-${e.slice(11)}`;if(e.startsWith("--ds-typography-"))return`--text-${e.slice(16)}`;return}function o(e){return`@theme inline {
5
- ${Object.keys(l(e)).sort().flatMap((r)=>{const t=d(r);return t?[` ${t}: var(${r});`]:[]}).join(`
3
+ ${t}
4
+ }`}function c(e){return Object.assign({},...Object.values(e.modes))}function d(e){if(e.startsWith("--ds-color-"))return`--color-${e.slice(11)}`;if(e.startsWith("--ds-radius-"))return`--radius-${e.slice(12)}`;if(e.startsWith("--ds-space-"))return`--spacing-${e.slice(11)}`;if(e.startsWith("--ds-typography-"))return`--text-${e.slice(16)}`;return}function u(e){if(!e.startsWith("--ds-color-"))return[];const s=e.slice(11);return(l[s]??[]).map((r)=>`--color-${r}`)}function h(e){return`@theme inline {
5
+ ${Object.keys(c(e)).sort().flatMap((t)=>{const r=d(t),i=u(t);return[...r?[` ${r}: var(${t});`]:[],...i.map((a)=>` ${a}: var(${t});`)]}).join(`
6
6
  `)}
7
- }`}export function themeSpecToTailwindCss(e,s={}){return[s.includeCustomVariant?"@custom-variant dark (&:where(.dark, .dark *));":void 0,n(s.rootSelector??":root",e.light),n(s.darkSelector??".dark",e.dark),...Object.entries(e.modes).filter(([t])=>!a.includes(t)).map(([t,i])=>n(`[data-theme="${t}"]`,i)),s.includeTheme===!1?void 0:o(e)].filter(Boolean).join(`
7
+ }`}export function themeSpecToTailwindCss(e,s={}){return[s.includeCustomVariant?"@custom-variant dark (&:where(.dark, .dark *));":void 0,n(s.rootSelector??":root",e.light),n(s.darkSelector??".dark",e.dark),...Object.entries(e.modes).filter(([r])=>!o.includes(r)).map(([r,i])=>n(`[data-theme="${r}"]`,i)),s.includeTheme===!1?void 0:h(e)].filter(Boolean).join(`
8
8
 
9
9
  `)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/lib.design-system",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "Design tokens and theming primitives",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -31,12 +31,12 @@
31
31
  "sideEffects": false,
32
32
  "tree-shake": true,
33
33
  "dependencies": {
34
- "@contractspec/lib.ai-agent": "8.0.11",
35
- "@contractspec/lib.contracts-spec": "5.6.0",
36
- "@contractspec/lib.contracts-runtime-client-react": "3.11.0",
37
- "@contractspec/lib.presentation-runtime-react": "38.0.3",
38
- "@contractspec/lib.ui-kit": "4.1.0",
39
- "@contractspec/lib.ui-kit-web": "3.12.0",
34
+ "@contractspec/lib.ai-agent": "8.0.13",
35
+ "@contractspec/lib.contracts-spec": "6.0.0",
36
+ "@contractspec/lib.contracts-runtime-client-react": "3.12.0",
37
+ "@contractspec/lib.presentation-runtime-react": "39.0.1",
38
+ "@contractspec/lib.ui-kit": "4.1.2",
39
+ "@contractspec/lib.ui-kit-web": "3.13.0",
40
40
  "@hookform/resolvers": "5.2.2",
41
41
  "class-variance-authority": "^0.7.1",
42
42
  "clsx": "^2.1.1",
@@ -464,6 +464,12 @@
464
464
  "bun": "./dist/components/forms/controls/Select.js",
465
465
  "default": "./dist/components/forms/controls/Select.js"
466
466
  },
467
+ "./components/forms/controls/select-options": {
468
+ "types": "./dist/components/forms/controls/select-options.d.ts",
469
+ "browser": "./dist/browser/components/forms/controls/select-options.js",
470
+ "bun": "./dist/components/forms/controls/select-options.js",
471
+ "default": "./dist/components/forms/controls/select-options.js"
472
+ },
467
473
  "./components/forms/controls/Select.native": {
468
474
  "types": "./dist/components/forms/controls/Select.native.d.ts",
469
475
  "react-native": "./dist/native/components/forms/controls/Select.native.js",
@@ -2019,6 +2025,12 @@
2019
2025
  "bun": "./dist/components/forms/controls/Select.js",
2020
2026
  "default": "./dist/components/forms/controls/Select.js"
2021
2027
  },
2028
+ "./components/forms/controls/select-options": {
2029
+ "types": "./dist/components/forms/controls/select-options.d.ts",
2030
+ "browser": "./dist/browser/components/forms/controls/select-options.js",
2031
+ "bun": "./dist/components/forms/controls/select-options.js",
2032
+ "default": "./dist/components/forms/controls/select-options.js"
2033
+ },
2022
2034
  "./components/forms/controls/Select.native": {
2023
2035
  "types": "./dist/components/forms/controls/Select.native.d.ts",
2024
2036
  "react-native": "./dist/native/components/forms/controls/Select.native.js",