@omnibase/shadcn 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +190 -23
- package/dist/index.js +192 -25
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -1886,19 +1886,96 @@ function CommandItem({
|
|
|
1886
1886
|
// src/permissions-selector/index.tsx
|
|
1887
1887
|
var import_lucide_react5 = require("lucide-react");
|
|
1888
1888
|
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
1889
|
+
function groupOptions(options) {
|
|
1890
|
+
const result = {
|
|
1891
|
+
ungrouped: [],
|
|
1892
|
+
groups: {}
|
|
1893
|
+
};
|
|
1894
|
+
for (const option of options) {
|
|
1895
|
+
if (!option.group) {
|
|
1896
|
+
result.ungrouped.push(option);
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
if (!result.groups[option.group]) {
|
|
1900
|
+
result.groups[option.group] = { options: [], subGroups: {} };
|
|
1901
|
+
}
|
|
1902
|
+
if (option.subGroup) {
|
|
1903
|
+
if (!result.groups[option.group].subGroups[option.subGroup]) {
|
|
1904
|
+
result.groups[option.group].subGroups[option.subGroup] = [];
|
|
1905
|
+
}
|
|
1906
|
+
result.groups[option.group].subGroups[option.subGroup].push(option);
|
|
1907
|
+
} else {
|
|
1908
|
+
result.groups[option.group].options.push(option);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
return result;
|
|
1912
|
+
}
|
|
1913
|
+
function renderCommandItem(option, value, onChange, setOpen, indent = 0) {
|
|
1914
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
1915
|
+
CommandItem,
|
|
1916
|
+
{
|
|
1917
|
+
value: option.label,
|
|
1918
|
+
onSelect: () => {
|
|
1919
|
+
onChange(option.value);
|
|
1920
|
+
setOpen(false);
|
|
1921
|
+
},
|
|
1922
|
+
className: indent > 0 ? "pl-8" : void 0,
|
|
1923
|
+
children: [
|
|
1924
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
1925
|
+
import_lucide_react5.Check,
|
|
1926
|
+
{
|
|
1927
|
+
className: cn(
|
|
1928
|
+
"mr-2 h-4 w-4",
|
|
1929
|
+
value === option.value ? "opacity-100" : "opacity-0"
|
|
1930
|
+
)
|
|
1931
|
+
}
|
|
1932
|
+
),
|
|
1933
|
+
option.label
|
|
1934
|
+
]
|
|
1935
|
+
},
|
|
1936
|
+
option.value
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1889
1939
|
function Combobox({
|
|
1890
1940
|
options,
|
|
1891
1941
|
value,
|
|
1892
1942
|
onChange,
|
|
1943
|
+
onSelectMultiple,
|
|
1893
1944
|
placeholder,
|
|
1894
1945
|
searchPlaceholder = "Search...",
|
|
1895
1946
|
emptyText = "No results found.",
|
|
1896
1947
|
disabled = false,
|
|
1897
|
-
className = ""
|
|
1948
|
+
className = "",
|
|
1949
|
+
grouped = false
|
|
1898
1950
|
}) {
|
|
1899
1951
|
const [open, setOpen] = (0, import_react3.useState)(false);
|
|
1900
1952
|
const selectedOption = options.find((opt) => opt.value === value);
|
|
1901
1953
|
const isEmpty = options.length === 0;
|
|
1954
|
+
const groupedOpts = (0, import_react3.useMemo)(
|
|
1955
|
+
() => grouped ? groupOptions(options) : null,
|
|
1956
|
+
[options, grouped]
|
|
1957
|
+
);
|
|
1958
|
+
const hasGroups = groupedOpts && Object.keys(groupedOpts.groups).length > 0;
|
|
1959
|
+
const handleSelectGroup = (groupName) => {
|
|
1960
|
+
if (!groupedOpts || !onSelectMultiple) return;
|
|
1961
|
+
const group = groupedOpts.groups[groupName];
|
|
1962
|
+
if (!group) return;
|
|
1963
|
+
const allValues = [
|
|
1964
|
+
...group.options.map((o) => o.value),
|
|
1965
|
+
...Object.values(group.subGroups).flatMap(
|
|
1966
|
+
(opts) => opts.map((o) => o.value)
|
|
1967
|
+
)
|
|
1968
|
+
];
|
|
1969
|
+
onSelectMultiple(allValues);
|
|
1970
|
+
setOpen(false);
|
|
1971
|
+
};
|
|
1972
|
+
const handleSelectSubGroup = (groupName, subGroupName) => {
|
|
1973
|
+
if (!groupedOpts || !onSelectMultiple) return;
|
|
1974
|
+
const subOpts = groupedOpts.groups[groupName]?.subGroups[subGroupName];
|
|
1975
|
+
if (!subOpts) return;
|
|
1976
|
+
onSelectMultiple(subOpts.map((o) => o.value));
|
|
1977
|
+
setOpen(false);
|
|
1978
|
+
};
|
|
1902
1979
|
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(Popover, { open, onOpenChange: setOpen, children: [
|
|
1903
1980
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
1904
1981
|
Button,
|
|
@@ -1927,29 +2004,69 @@ function Combobox({
|
|
|
1927
2004
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(CommandInput, { placeholder: searchPlaceholder }),
|
|
1928
2005
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(CommandList, { children: [
|
|
1929
2006
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(CommandEmpty, { children: emptyText }),
|
|
1930
|
-
/* @__PURE__ */ (0, import_jsx_runtime29.
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2007
|
+
grouped && hasGroups ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
|
|
2008
|
+
groupedOpts.ungrouped.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(CommandGroup, { children: groupedOpts.ungrouped.map(
|
|
2009
|
+
(opt) => renderCommandItem(opt, value, onChange, setOpen)
|
|
2010
|
+
) }),
|
|
2011
|
+
Object.entries(groupedOpts.groups).map(
|
|
2012
|
+
([groupName, group]) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
2013
|
+
CommandGroup,
|
|
2014
|
+
{
|
|
2015
|
+
heading: onSelectMultiple ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
2016
|
+
"button",
|
|
2017
|
+
{
|
|
2018
|
+
type: "button",
|
|
2019
|
+
onClick: (e) => {
|
|
2020
|
+
e.preventDefault();
|
|
2021
|
+
e.stopPropagation();
|
|
2022
|
+
handleSelectGroup(groupName);
|
|
2023
|
+
},
|
|
2024
|
+
className: "flex items-center gap-1.5 hover:text-foreground transition-colors group w-full text-left",
|
|
2025
|
+
children: [
|
|
2026
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react5.Plus, { className: "h-3 w-3 opacity-0 group-hover:opacity-70 transition-opacity" }),
|
|
2027
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { children: groupName })
|
|
2028
|
+
]
|
|
2029
|
+
}
|
|
2030
|
+
) : groupName,
|
|
2031
|
+
children: [
|
|
2032
|
+
group.options.map(
|
|
2033
|
+
(opt) => renderCommandItem(opt, value, onChange, setOpen)
|
|
2034
|
+
),
|
|
2035
|
+
Object.entries(group.subGroups).map(
|
|
2036
|
+
([subGroupName, subOpts]) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "mt-1", children: [
|
|
2037
|
+
onSelectMultiple ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
2038
|
+
"button",
|
|
2039
|
+
{
|
|
2040
|
+
type: "button",
|
|
2041
|
+
onClick: (e) => {
|
|
2042
|
+
e.preventDefault();
|
|
2043
|
+
e.stopPropagation();
|
|
2044
|
+
handleSelectSubGroup(groupName, subGroupName);
|
|
2045
|
+
},
|
|
2046
|
+
className: "flex items-center gap-1.5 px-2 py-1 text-[11px] text-muted-foreground/70 uppercase tracking-wider hover:text-muted-foreground transition-colors group w-full text-left",
|
|
2047
|
+
children: [
|
|
2048
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-muted-foreground/40 group-hover:text-muted-foreground/70", children: "\u2014" }),
|
|
2049
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react5.Plus, { className: "h-2.5 w-2.5 opacity-0 group-hover:opacity-70 transition-opacity" }),
|
|
2050
|
+
subGroupName
|
|
2051
|
+
]
|
|
2052
|
+
}
|
|
2053
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-1.5 px-2 py-1 text-[11px] text-muted-foreground/70 uppercase tracking-wider", children: [
|
|
2054
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-muted-foreground/40", children: "\u2014" }),
|
|
2055
|
+
subGroupName
|
|
2056
|
+
] }),
|
|
2057
|
+
subOpts.map(
|
|
2058
|
+
(opt) => renderCommandItem(opt, value, onChange, setOpen, 1)
|
|
2059
|
+
)
|
|
2060
|
+
] }, subGroupName)
|
|
1945
2061
|
)
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2062
|
+
]
|
|
2063
|
+
},
|
|
2064
|
+
groupName
|
|
2065
|
+
)
|
|
2066
|
+
)
|
|
2067
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(CommandGroup, { children: options.map(
|
|
2068
|
+
(option) => renderCommandItem(option, value, onChange, setOpen)
|
|
2069
|
+
) })
|
|
1953
2070
|
] })
|
|
1954
2071
|
] })
|
|
1955
2072
|
}
|
|
@@ -1992,8 +2109,20 @@ function PermissionsSelector({
|
|
|
1992
2109
|
const getRelationsForNamespace = (namespace) => {
|
|
1993
2110
|
const def = definitions.find((d) => d.namespace === namespace);
|
|
1994
2111
|
if (!def) return [];
|
|
2112
|
+
if (def.relationsMetadata && def.relationsMetadata.length > 0) {
|
|
2113
|
+
return def.relationsMetadata.map((rm) => ({
|
|
2114
|
+
value: rm.name,
|
|
2115
|
+
label: rm.displayName,
|
|
2116
|
+
group: rm.group ?? null,
|
|
2117
|
+
subGroup: rm.subGroup ?? null
|
|
2118
|
+
}));
|
|
2119
|
+
}
|
|
1995
2120
|
return def.relations.filter((rel) => rel.startsWith("can_") || rel.startsWith("is_")).map((rel) => ({ value: rel, label: formatRelation(rel) }));
|
|
1996
2121
|
};
|
|
2122
|
+
const hasRelationsMetadata = (namespace) => {
|
|
2123
|
+
const def = definitions.find((d) => d.namespace === namespace);
|
|
2124
|
+
return !!(def?.relationsMetadata && def.relationsMetadata.length > 0);
|
|
2125
|
+
};
|
|
1997
2126
|
const getObjectsForNamespace = (namespace) => {
|
|
1998
2127
|
if (namespace.toLowerCase() === "tenant") {
|
|
1999
2128
|
return null;
|
|
@@ -2034,6 +2163,42 @@ function PermissionsSelector({
|
|
|
2034
2163
|
return finalRows;
|
|
2035
2164
|
});
|
|
2036
2165
|
};
|
|
2166
|
+
const addMultiplePermissions = (rowId, namespace, relations) => {
|
|
2167
|
+
if (relations.length === 0) return;
|
|
2168
|
+
setPermissionRows((currentRows) => {
|
|
2169
|
+
const rowIndex = currentRows.findIndex((r) => r.id === rowId);
|
|
2170
|
+
if (rowIndex === -1) return currentRows;
|
|
2171
|
+
const existingRelations = new Set(
|
|
2172
|
+
currentRows.filter((r) => r.namespace === namespace && r.relation).map((r) => r.relation)
|
|
2173
|
+
);
|
|
2174
|
+
const newRelations = relations.filter((r) => !existingRelations.has(r));
|
|
2175
|
+
if (newRelations.length === 0) return currentRows;
|
|
2176
|
+
const updatedRows = [...currentRows];
|
|
2177
|
+
updatedRows[rowIndex] = {
|
|
2178
|
+
...updatedRows[rowIndex],
|
|
2179
|
+
namespace,
|
|
2180
|
+
relation: newRelations[0]
|
|
2181
|
+
};
|
|
2182
|
+
const additionalRows = newRelations.slice(1).map((relation) => ({
|
|
2183
|
+
id: generateId(),
|
|
2184
|
+
namespace,
|
|
2185
|
+
relation,
|
|
2186
|
+
objectId: ""
|
|
2187
|
+
}));
|
|
2188
|
+
updatedRows.splice(rowIndex + 1, 0, ...additionalRows);
|
|
2189
|
+
const lastRow = updatedRows[updatedRows.length - 1];
|
|
2190
|
+
if (hasAnyValue(lastRow)) {
|
|
2191
|
+
updatedRows.push({
|
|
2192
|
+
id: generateId(),
|
|
2193
|
+
namespace: "",
|
|
2194
|
+
relation: "",
|
|
2195
|
+
objectId: ""
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
onPermissionsChange?.(updatedRows);
|
|
2199
|
+
return updatedRows;
|
|
2200
|
+
});
|
|
2201
|
+
};
|
|
2037
2202
|
const buildPermissions = () => {
|
|
2038
2203
|
return permissionRows.filter((row) => row.namespace && row.relation).map((row) => buildPermissionString(row)).filter((perm) => perm !== "");
|
|
2039
2204
|
};
|
|
@@ -2079,9 +2244,11 @@ function PermissionsSelector({
|
|
|
2079
2244
|
options: relationOptions,
|
|
2080
2245
|
value: row.relation,
|
|
2081
2246
|
onChange: (value) => updatePermissionRow(row.id, "relation", value),
|
|
2247
|
+
onSelectMultiple: (relations) => addMultiplePermissions(row.id, row.namespace, relations),
|
|
2082
2248
|
placeholder: "Permission",
|
|
2083
2249
|
searchPlaceholder: "Search permissions...",
|
|
2084
2250
|
disabled: disabled || !row.namespace,
|
|
2251
|
+
grouped: hasRelationsMetadata(row.namespace),
|
|
2085
2252
|
className: cn(
|
|
2086
2253
|
"min-w-40 ease-out duration-450",
|
|
2087
2254
|
showObjectSelect ? "w-1/3 rounded-none border-r-0" : "w-2/3 rounded-l-none"
|
package/dist/index.js
CHANGED
|
@@ -1827,21 +1827,98 @@ function CommandItem({
|
|
|
1827
1827
|
}
|
|
1828
1828
|
|
|
1829
1829
|
// src/permissions-selector/index.tsx
|
|
1830
|
-
import { Trash2, Check as Check3, ChevronsUpDown } from "lucide-react";
|
|
1831
|
-
import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1830
|
+
import { Trash2, Check as Check3, ChevronsUpDown, Plus } from "lucide-react";
|
|
1831
|
+
import { Fragment as Fragment3, jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1832
|
+
function groupOptions(options) {
|
|
1833
|
+
const result = {
|
|
1834
|
+
ungrouped: [],
|
|
1835
|
+
groups: {}
|
|
1836
|
+
};
|
|
1837
|
+
for (const option of options) {
|
|
1838
|
+
if (!option.group) {
|
|
1839
|
+
result.ungrouped.push(option);
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
if (!result.groups[option.group]) {
|
|
1843
|
+
result.groups[option.group] = { options: [], subGroups: {} };
|
|
1844
|
+
}
|
|
1845
|
+
if (option.subGroup) {
|
|
1846
|
+
if (!result.groups[option.group].subGroups[option.subGroup]) {
|
|
1847
|
+
result.groups[option.group].subGroups[option.subGroup] = [];
|
|
1848
|
+
}
|
|
1849
|
+
result.groups[option.group].subGroups[option.subGroup].push(option);
|
|
1850
|
+
} else {
|
|
1851
|
+
result.groups[option.group].options.push(option);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
return result;
|
|
1855
|
+
}
|
|
1856
|
+
function renderCommandItem(option, value, onChange, setOpen, indent = 0) {
|
|
1857
|
+
return /* @__PURE__ */ jsxs18(
|
|
1858
|
+
CommandItem,
|
|
1859
|
+
{
|
|
1860
|
+
value: option.label,
|
|
1861
|
+
onSelect: () => {
|
|
1862
|
+
onChange(option.value);
|
|
1863
|
+
setOpen(false);
|
|
1864
|
+
},
|
|
1865
|
+
className: indent > 0 ? "pl-8" : void 0,
|
|
1866
|
+
children: [
|
|
1867
|
+
/* @__PURE__ */ jsx29(
|
|
1868
|
+
Check3,
|
|
1869
|
+
{
|
|
1870
|
+
className: cn(
|
|
1871
|
+
"mr-2 h-4 w-4",
|
|
1872
|
+
value === option.value ? "opacity-100" : "opacity-0"
|
|
1873
|
+
)
|
|
1874
|
+
}
|
|
1875
|
+
),
|
|
1876
|
+
option.label
|
|
1877
|
+
]
|
|
1878
|
+
},
|
|
1879
|
+
option.value
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1832
1882
|
function Combobox({
|
|
1833
1883
|
options,
|
|
1834
1884
|
value,
|
|
1835
1885
|
onChange,
|
|
1886
|
+
onSelectMultiple,
|
|
1836
1887
|
placeholder,
|
|
1837
1888
|
searchPlaceholder = "Search...",
|
|
1838
1889
|
emptyText = "No results found.",
|
|
1839
1890
|
disabled = false,
|
|
1840
|
-
className = ""
|
|
1891
|
+
className = "",
|
|
1892
|
+
grouped = false
|
|
1841
1893
|
}) {
|
|
1842
1894
|
const [open, setOpen] = useState6(false);
|
|
1843
1895
|
const selectedOption = options.find((opt) => opt.value === value);
|
|
1844
1896
|
const isEmpty = options.length === 0;
|
|
1897
|
+
const groupedOpts = useMemo2(
|
|
1898
|
+
() => grouped ? groupOptions(options) : null,
|
|
1899
|
+
[options, grouped]
|
|
1900
|
+
);
|
|
1901
|
+
const hasGroups = groupedOpts && Object.keys(groupedOpts.groups).length > 0;
|
|
1902
|
+
const handleSelectGroup = (groupName) => {
|
|
1903
|
+
if (!groupedOpts || !onSelectMultiple) return;
|
|
1904
|
+
const group = groupedOpts.groups[groupName];
|
|
1905
|
+
if (!group) return;
|
|
1906
|
+
const allValues = [
|
|
1907
|
+
...group.options.map((o) => o.value),
|
|
1908
|
+
...Object.values(group.subGroups).flatMap(
|
|
1909
|
+
(opts) => opts.map((o) => o.value)
|
|
1910
|
+
)
|
|
1911
|
+
];
|
|
1912
|
+
onSelectMultiple(allValues);
|
|
1913
|
+
setOpen(false);
|
|
1914
|
+
};
|
|
1915
|
+
const handleSelectSubGroup = (groupName, subGroupName) => {
|
|
1916
|
+
if (!groupedOpts || !onSelectMultiple) return;
|
|
1917
|
+
const subOpts = groupedOpts.groups[groupName]?.subGroups[subGroupName];
|
|
1918
|
+
if (!subOpts) return;
|
|
1919
|
+
onSelectMultiple(subOpts.map((o) => o.value));
|
|
1920
|
+
setOpen(false);
|
|
1921
|
+
};
|
|
1845
1922
|
return /* @__PURE__ */ jsxs18(Popover, { open, onOpenChange: setOpen, children: [
|
|
1846
1923
|
/* @__PURE__ */ jsx29(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs18(
|
|
1847
1924
|
Button,
|
|
@@ -1870,29 +1947,69 @@ function Combobox({
|
|
|
1870
1947
|
/* @__PURE__ */ jsx29(CommandInput, { placeholder: searchPlaceholder }),
|
|
1871
1948
|
/* @__PURE__ */ jsxs18(CommandList, { children: [
|
|
1872
1949
|
/* @__PURE__ */ jsx29(CommandEmpty, { children: emptyText }),
|
|
1873
|
-
/* @__PURE__ */
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1950
|
+
grouped && hasGroups ? /* @__PURE__ */ jsxs18(Fragment3, { children: [
|
|
1951
|
+
groupedOpts.ungrouped.length > 0 && /* @__PURE__ */ jsx29(CommandGroup, { children: groupedOpts.ungrouped.map(
|
|
1952
|
+
(opt) => renderCommandItem(opt, value, onChange, setOpen)
|
|
1953
|
+
) }),
|
|
1954
|
+
Object.entries(groupedOpts.groups).map(
|
|
1955
|
+
([groupName, group]) => /* @__PURE__ */ jsxs18(
|
|
1956
|
+
CommandGroup,
|
|
1957
|
+
{
|
|
1958
|
+
heading: onSelectMultiple ? /* @__PURE__ */ jsxs18(
|
|
1959
|
+
"button",
|
|
1960
|
+
{
|
|
1961
|
+
type: "button",
|
|
1962
|
+
onClick: (e) => {
|
|
1963
|
+
e.preventDefault();
|
|
1964
|
+
e.stopPropagation();
|
|
1965
|
+
handleSelectGroup(groupName);
|
|
1966
|
+
},
|
|
1967
|
+
className: "flex items-center gap-1.5 hover:text-foreground transition-colors group w-full text-left",
|
|
1968
|
+
children: [
|
|
1969
|
+
/* @__PURE__ */ jsx29(Plus, { className: "h-3 w-3 opacity-0 group-hover:opacity-70 transition-opacity" }),
|
|
1970
|
+
/* @__PURE__ */ jsx29("span", { children: groupName })
|
|
1971
|
+
]
|
|
1972
|
+
}
|
|
1973
|
+
) : groupName,
|
|
1974
|
+
children: [
|
|
1975
|
+
group.options.map(
|
|
1976
|
+
(opt) => renderCommandItem(opt, value, onChange, setOpen)
|
|
1977
|
+
),
|
|
1978
|
+
Object.entries(group.subGroups).map(
|
|
1979
|
+
([subGroupName, subOpts]) => /* @__PURE__ */ jsxs18("div", { className: "mt-1", children: [
|
|
1980
|
+
onSelectMultiple ? /* @__PURE__ */ jsxs18(
|
|
1981
|
+
"button",
|
|
1982
|
+
{
|
|
1983
|
+
type: "button",
|
|
1984
|
+
onClick: (e) => {
|
|
1985
|
+
e.preventDefault();
|
|
1986
|
+
e.stopPropagation();
|
|
1987
|
+
handleSelectSubGroup(groupName, subGroupName);
|
|
1988
|
+
},
|
|
1989
|
+
className: "flex items-center gap-1.5 px-2 py-1 text-[11px] text-muted-foreground/70 uppercase tracking-wider hover:text-muted-foreground transition-colors group w-full text-left",
|
|
1990
|
+
children: [
|
|
1991
|
+
/* @__PURE__ */ jsx29("span", { className: "text-muted-foreground/40 group-hover:text-muted-foreground/70", children: "\u2014" }),
|
|
1992
|
+
/* @__PURE__ */ jsx29(Plus, { className: "h-2.5 w-2.5 opacity-0 group-hover:opacity-70 transition-opacity" }),
|
|
1993
|
+
subGroupName
|
|
1994
|
+
]
|
|
1995
|
+
}
|
|
1996
|
+
) : /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1.5 px-2 py-1 text-[11px] text-muted-foreground/70 uppercase tracking-wider", children: [
|
|
1997
|
+
/* @__PURE__ */ jsx29("span", { className: "text-muted-foreground/40", children: "\u2014" }),
|
|
1998
|
+
subGroupName
|
|
1999
|
+
] }),
|
|
2000
|
+
subOpts.map(
|
|
2001
|
+
(opt) => renderCommandItem(opt, value, onChange, setOpen, 1)
|
|
2002
|
+
)
|
|
2003
|
+
] }, subGroupName)
|
|
1888
2004
|
)
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2005
|
+
]
|
|
2006
|
+
},
|
|
2007
|
+
groupName
|
|
2008
|
+
)
|
|
2009
|
+
)
|
|
2010
|
+
] }) : /* @__PURE__ */ jsx29(CommandGroup, { children: options.map(
|
|
2011
|
+
(option) => renderCommandItem(option, value, onChange, setOpen)
|
|
2012
|
+
) })
|
|
1896
2013
|
] })
|
|
1897
2014
|
] })
|
|
1898
2015
|
}
|
|
@@ -1935,8 +2052,20 @@ function PermissionsSelector({
|
|
|
1935
2052
|
const getRelationsForNamespace = (namespace) => {
|
|
1936
2053
|
const def = definitions.find((d) => d.namespace === namespace);
|
|
1937
2054
|
if (!def) return [];
|
|
2055
|
+
if (def.relationsMetadata && def.relationsMetadata.length > 0) {
|
|
2056
|
+
return def.relationsMetadata.map((rm) => ({
|
|
2057
|
+
value: rm.name,
|
|
2058
|
+
label: rm.displayName,
|
|
2059
|
+
group: rm.group ?? null,
|
|
2060
|
+
subGroup: rm.subGroup ?? null
|
|
2061
|
+
}));
|
|
2062
|
+
}
|
|
1938
2063
|
return def.relations.filter((rel) => rel.startsWith("can_") || rel.startsWith("is_")).map((rel) => ({ value: rel, label: formatRelation(rel) }));
|
|
1939
2064
|
};
|
|
2065
|
+
const hasRelationsMetadata = (namespace) => {
|
|
2066
|
+
const def = definitions.find((d) => d.namespace === namespace);
|
|
2067
|
+
return !!(def?.relationsMetadata && def.relationsMetadata.length > 0);
|
|
2068
|
+
};
|
|
1940
2069
|
const getObjectsForNamespace = (namespace) => {
|
|
1941
2070
|
if (namespace.toLowerCase() === "tenant") {
|
|
1942
2071
|
return null;
|
|
@@ -1977,6 +2106,42 @@ function PermissionsSelector({
|
|
|
1977
2106
|
return finalRows;
|
|
1978
2107
|
});
|
|
1979
2108
|
};
|
|
2109
|
+
const addMultiplePermissions = (rowId, namespace, relations) => {
|
|
2110
|
+
if (relations.length === 0) return;
|
|
2111
|
+
setPermissionRows((currentRows) => {
|
|
2112
|
+
const rowIndex = currentRows.findIndex((r) => r.id === rowId);
|
|
2113
|
+
if (rowIndex === -1) return currentRows;
|
|
2114
|
+
const existingRelations = new Set(
|
|
2115
|
+
currentRows.filter((r) => r.namespace === namespace && r.relation).map((r) => r.relation)
|
|
2116
|
+
);
|
|
2117
|
+
const newRelations = relations.filter((r) => !existingRelations.has(r));
|
|
2118
|
+
if (newRelations.length === 0) return currentRows;
|
|
2119
|
+
const updatedRows = [...currentRows];
|
|
2120
|
+
updatedRows[rowIndex] = {
|
|
2121
|
+
...updatedRows[rowIndex],
|
|
2122
|
+
namespace,
|
|
2123
|
+
relation: newRelations[0]
|
|
2124
|
+
};
|
|
2125
|
+
const additionalRows = newRelations.slice(1).map((relation) => ({
|
|
2126
|
+
id: generateId(),
|
|
2127
|
+
namespace,
|
|
2128
|
+
relation,
|
|
2129
|
+
objectId: ""
|
|
2130
|
+
}));
|
|
2131
|
+
updatedRows.splice(rowIndex + 1, 0, ...additionalRows);
|
|
2132
|
+
const lastRow = updatedRows[updatedRows.length - 1];
|
|
2133
|
+
if (hasAnyValue(lastRow)) {
|
|
2134
|
+
updatedRows.push({
|
|
2135
|
+
id: generateId(),
|
|
2136
|
+
namespace: "",
|
|
2137
|
+
relation: "",
|
|
2138
|
+
objectId: ""
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
onPermissionsChange?.(updatedRows);
|
|
2142
|
+
return updatedRows;
|
|
2143
|
+
});
|
|
2144
|
+
};
|
|
1980
2145
|
const buildPermissions = () => {
|
|
1981
2146
|
return permissionRows.filter((row) => row.namespace && row.relation).map((row) => buildPermissionString(row)).filter((perm) => perm !== "");
|
|
1982
2147
|
};
|
|
@@ -2022,9 +2187,11 @@ function PermissionsSelector({
|
|
|
2022
2187
|
options: relationOptions,
|
|
2023
2188
|
value: row.relation,
|
|
2024
2189
|
onChange: (value) => updatePermissionRow(row.id, "relation", value),
|
|
2190
|
+
onSelectMultiple: (relations) => addMultiplePermissions(row.id, row.namespace, relations),
|
|
2025
2191
|
placeholder: "Permission",
|
|
2026
2192
|
searchPlaceholder: "Search permissions...",
|
|
2027
2193
|
disabled: disabled || !row.namespace,
|
|
2194
|
+
grouped: hasRelationsMetadata(row.namespace),
|
|
2028
2195
|
className: cn(
|
|
2029
2196
|
"min-w-40 ease-out duration-450",
|
|
2030
2197
|
showObjectSelect ? "w-1/3 rounded-none border-r-0" : "w-2/3 rounded-l-none"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnibase/shadcn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "OmniBase ShadCN UI Package",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,9 +40,10 @@
|
|
|
40
40
|
"react-dom": "^19.1.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@omnibase/core-js": "0.
|
|
43
|
+
"@omnibase/core-js": "0.17.0",
|
|
44
44
|
"@radix-ui/react-avatar": "^1.1.11",
|
|
45
45
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
46
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
46
47
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
47
48
|
"@radix-ui/react-label": "^2.1.8",
|
|
48
49
|
"@radix-ui/react-popover": "^1.1.15",
|