@meetreeve/ui 0.9.0 → 0.10.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.mjs CHANGED
@@ -1372,7 +1372,17 @@ var DEFAULT_GRANTS_LOAD_ERROR = "Couldn't load sharing. Please try again.";
1372
1372
  var DEFAULT_GRANT_ERROR = "Couldn't share. Please try again.";
1373
1373
  var DEFAULT_REVOKE_ERROR = "Couldn't revoke access. Please try again.";
1374
1374
  var DEFAULT_ORG_DISABLED_REASON = "Org-wide connections aren't available to you.";
1375
+ var DEFAULT_CREDENTIAL_ERROR = "Couldn't save that credential. Please try again.";
1376
+ var DEFAULT_CREDENTIAL_TEST_ERROR = "Couldn't test that credential. Please try again.";
1377
+ var UNVERIFIABLE_CREDENTIAL_NOTE = "We can't verify this automatically \u2014 saving it is safe, but nothing has checked it yet.";
1375
1378
  var SKELETON_COUNT = 3;
1379
+ var UNGROUNDED_PLACEHOLDER = /\{\{[^}]*\}\}/;
1380
+ function hasUngroundedPlaceholder(text) {
1381
+ return UNGROUNDED_PLACEHOLDER.test(text);
1382
+ }
1383
+ function isDevEnvironment() {
1384
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1385
+ }
1376
1386
  function capabilityLabel(capability) {
1377
1387
  if (capability === "read") return "Read only";
1378
1388
  if (capability === "write") return "Write only";
@@ -1409,6 +1419,8 @@ function ConnectorPicker({
1409
1419
  const [loadError, setLoadError] = useState7(null);
1410
1420
  const [uiState, setUiState] = useState7({});
1411
1421
  const grantsSupported = typeof client.listGrants === "function" && typeof client.grant === "function" && typeof client.revokeGrant === "function";
1422
+ const credentialsSupported = typeof client.submitCredentials === "function";
1423
+ const credentialTestSupported = typeof client.testCredentials === "function";
1412
1424
  const clientRef = useRef5(client);
1413
1425
  clientRef.current = client;
1414
1426
  const purposeRef = useRef5(purpose);
@@ -1492,6 +1504,38 @@ function ConnectorPicker({
1492
1504
  },
1493
1505
  [load]
1494
1506
  );
1507
+ const handleSubmitCredentials = useCallback4(
1508
+ async (entry, values, kind) => {
1509
+ setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
1510
+ try {
1511
+ await clientRef.current.submitCredentials?.(entry, values, { kind });
1512
+ const list = await load();
1513
+ const updated = list?.find((e) => e.slug === entry.slug) ?? null;
1514
+ setUiState((prev) => {
1515
+ const next = { ...prev };
1516
+ delete next[entry.slug];
1517
+ return next;
1518
+ });
1519
+ if (updated?.connection.status === "connected") {
1520
+ onConnectedRef.current?.(updated);
1521
+ } else {
1522
+ throw new Error(DEFAULT_CREDENTIAL_ERROR);
1523
+ }
1524
+ } catch (err) {
1525
+ setUiState((prev) => {
1526
+ const next = { ...prev };
1527
+ delete next[entry.slug];
1528
+ return next;
1529
+ });
1530
+ throw err;
1531
+ }
1532
+ },
1533
+ [load]
1534
+ );
1535
+ const testCredentials = useCallback4(
1536
+ (entry, values) => clientRef.current.testCredentials?.(entry, values) ?? Promise.resolve({ valid: null }),
1537
+ []
1538
+ );
1495
1539
  const handleDisconnect = useCallback4(
1496
1540
  async (entry) => {
1497
1541
  setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "disconnecting" } }));
@@ -1564,8 +1608,12 @@ function ConnectorPicker({
1564
1608
  ui: uiState[entry.slug],
1565
1609
  layout,
1566
1610
  grantsSupported,
1611
+ credentialsSupported,
1612
+ credentialTestSupported,
1567
1613
  onConnect: handleConnect,
1568
1614
  onDisconnect: handleDisconnect,
1615
+ onSubmitCredentials: handleSubmitCredentials,
1616
+ onTestCredentials: testCredentials,
1569
1617
  listGrants,
1570
1618
  addGrant,
1571
1619
  revokeGrant
@@ -1583,8 +1631,12 @@ function ConnectorPicker({
1583
1631
  layout,
1584
1632
  shared: true,
1585
1633
  grantsSupported,
1634
+ credentialsSupported,
1635
+ credentialTestSupported,
1586
1636
  onConnect: handleConnect,
1587
1637
  onDisconnect: handleDisconnect,
1638
+ onSubmitCredentials: handleSubmitCredentials,
1639
+ onTestCredentials: testCredentials,
1588
1640
  listGrants,
1589
1641
  addGrant,
1590
1642
  revokeGrant
@@ -1600,8 +1652,12 @@ function ConnectorTile({
1600
1652
  layout,
1601
1653
  shared = false,
1602
1654
  grantsSupported,
1655
+ credentialsSupported,
1656
+ credentialTestSupported,
1603
1657
  onConnect,
1604
1658
  onDisconnect,
1659
+ onSubmitCredentials,
1660
+ onTestCredentials,
1605
1661
  listGrants,
1606
1662
  addGrant,
1607
1663
  revokeGrant
@@ -1612,6 +1668,11 @@ function ConnectorTile({
1612
1668
  const errorMessage = ui?.kind === "error" ? ui.message : null;
1613
1669
  const [kind, setKind] = useState7("personal");
1614
1670
  const [sharingOpen, setSharingOpen] = useState7(false);
1671
+ const [setupOpen, setSetupOpen] = useState7(false);
1672
+ const credentialFields = entry.credential_fields ?? [];
1673
+ const pasteSupported = credentialsSupported && credentialFields.length > 0;
1674
+ const guide = entry.guide ?? null;
1675
+ const canSetUp = !isConnected && (pasteSupported || guide !== null);
1615
1676
  const orgPermitted = entry.org_connect?.permitted === true;
1616
1677
  const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
1617
1678
  const attribution = ownerLabel(entry);
@@ -1738,18 +1799,55 @@ function ConnectorTile({
1738
1799
  children: sharingOpen ? "Hide sharing" : "Manage sharing"
1739
1800
  }
1740
1801
  )
1741
- ] }) : /* @__PURE__ */ jsx7(
1742
- "button",
1743
- {
1744
- type: "button",
1745
- "data-testid": `connector-connect-${entry.slug}`,
1746
- onClick: () => onConnect(entry, kind),
1747
- disabled: isConnecting,
1748
- className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
1749
- children: isConnecting ? "Connecting\u2026" : "Connect"
1750
- }
1751
- )
1802
+ ] }) : /* @__PURE__ */ jsxs6("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
1803
+ /* @__PURE__ */ jsx7(
1804
+ "button",
1805
+ {
1806
+ type: "button",
1807
+ "data-testid": `connector-connect-${entry.slug}`,
1808
+ onClick: () => onConnect(entry, kind),
1809
+ disabled: isConnecting,
1810
+ className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
1811
+ children: isConnecting ? "Connecting\u2026" : "Connect"
1812
+ }
1813
+ ),
1814
+ canSetUp && /* @__PURE__ */ jsx7(
1815
+ "button",
1816
+ {
1817
+ type: "button",
1818
+ "data-testid": `connector-setup-toggle-${entry.slug}`,
1819
+ "aria-expanded": setupOpen,
1820
+ "aria-controls": `connector-setup-panel-${entry.slug}`,
1821
+ onClick: () => setSetupOpen((open) => !open),
1822
+ className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
1823
+ children: setupOpen ? "Hide setup" : pasteSupported ? "Use a token instead" : "Setup guide"
1824
+ }
1825
+ )
1826
+ ] })
1752
1827
  ] }),
1828
+ canSetUp && setupOpen && /* @__PURE__ */ jsxs6(
1829
+ "div",
1830
+ {
1831
+ id: `connector-setup-panel-${entry.slug}`,
1832
+ "data-testid": `connector-setup-panel-${entry.slug}`,
1833
+ className: "mt-2 flex flex-col gap-3 rounded-md border border-border bg-foreground/5 p-2",
1834
+ children: [
1835
+ guide && /* @__PURE__ */ jsx7(SetupGuidePanel, { entry, guide, showVerify: !pasteSupported }),
1836
+ pasteSupported && /* @__PURE__ */ jsx7(
1837
+ CredentialForm,
1838
+ {
1839
+ entry,
1840
+ fields: credentialFields,
1841
+ kind,
1842
+ verify: guide?.verify ?? null,
1843
+ testSupported: credentialTestSupported,
1844
+ onSubmitCredentials,
1845
+ onTestCredentials
1846
+ }
1847
+ )
1848
+ ]
1849
+ }
1850
+ ),
1753
1851
  canManageSharing && sharingOpen && /* @__PURE__ */ jsx7(
1754
1852
  ManageSharing,
1755
1853
  {
@@ -1763,6 +1861,333 @@ function ConnectorTile({
1763
1861
  }
1764
1862
  );
1765
1863
  }
1864
+ function groundGuide(guide) {
1865
+ const ungrounded = [];
1866
+ const keep = (text) => {
1867
+ if (!hasUngroundedPlaceholder(text)) return true;
1868
+ ungrounded.push(text);
1869
+ return false;
1870
+ };
1871
+ const prerequisites = guide.prerequisites.filter(keep);
1872
+ const steps = guide.steps.filter(
1873
+ (step) => keep(step.text) && (!step.copyable || keep(step.copyable))
1874
+ );
1875
+ const scopeNote = keep(guide.scope_note) ? guide.scope_note : null;
1876
+ const verify = keep(guide.verify) ? guide.verify : null;
1877
+ return { prerequisites, steps, scopeNote, verify, ungrounded };
1878
+ }
1879
+ function CopyableValue({ value, testId }) {
1880
+ const [copied, setCopied] = useState7(false);
1881
+ const timer = useRef5(null);
1882
+ useEffect6(
1883
+ () => () => {
1884
+ if (timer.current) clearTimeout(timer.current);
1885
+ },
1886
+ []
1887
+ );
1888
+ const copy = useCallback4(async () => {
1889
+ try {
1890
+ if (typeof navigator.clipboard?.writeText !== "function") {
1891
+ throw new Error("clipboard unavailable");
1892
+ }
1893
+ await navigator.clipboard.writeText(value);
1894
+ setCopied(true);
1895
+ if (timer.current) clearTimeout(timer.current);
1896
+ timer.current = setTimeout(() => setCopied(false), 1500);
1897
+ } catch {
1898
+ setCopied(false);
1899
+ }
1900
+ }, [value]);
1901
+ return /* @__PURE__ */ jsxs6("span", { className: "mt-1 flex items-center gap-1.5", children: [
1902
+ /* @__PURE__ */ jsx7(
1903
+ "code",
1904
+ {
1905
+ "data-testid": `${testId}-value`,
1906
+ className: "min-w-0 flex-1 truncate rounded bg-foreground/10 px-1.5 py-0.5 text-[11px]",
1907
+ children: value
1908
+ }
1909
+ ),
1910
+ /* @__PURE__ */ jsx7(
1911
+ "button",
1912
+ {
1913
+ type: "button",
1914
+ "data-testid": testId,
1915
+ onClick: () => void copy(),
1916
+ className: "shrink-0 rounded-md px-1.5 py-0.5 text-[11px] font-medium text-muted-foreground hover:bg-foreground/10",
1917
+ children: copied ? "Copied" : "Copy"
1918
+ }
1919
+ )
1920
+ ] });
1921
+ }
1922
+ function SetupGuidePanel({
1923
+ entry,
1924
+ guide,
1925
+ showVerify
1926
+ }) {
1927
+ const { prerequisites, steps, scopeNote, verify, ungrounded } = groundGuide(guide);
1928
+ const leaked = ungrounded.length > 0;
1929
+ useEffect6(() => {
1930
+ if (!leaked) return;
1931
+ console.error(
1932
+ `[ConnectorPicker] setup guide for "${entry.slug}" arrived with ungrounded placeholders:`,
1933
+ ungrounded
1934
+ );
1935
+ }, [entry.slug, guide, leaked]);
1936
+ return /* @__PURE__ */ jsxs6("div", { "data-testid": `connector-guide-${entry.slug}`, className: "flex flex-col gap-2", children: [
1937
+ leaked && isDevEnvironment() && /* @__PURE__ */ jsxs6(
1938
+ "p",
1939
+ {
1940
+ role: "alert",
1941
+ "data-testid": `connector-guide-ungrounded-${entry.slug}`,
1942
+ className: "rounded-md border border-destructive/30 bg-destructive/5 px-2 py-1 text-xs text-destructive",
1943
+ children: [
1944
+ "Setup guide arrived with unresolved placeholders and those lines were dropped: ",
1945
+ ungrounded.join(" \xB7 ")
1946
+ ]
1947
+ }
1948
+ ),
1949
+ prerequisites.length > 0 && /* @__PURE__ */ jsxs6("div", { children: [
1950
+ /* @__PURE__ */ jsx7("h4", { className: "text-xs font-medium", children: "Before you start" }),
1951
+ /* @__PURE__ */ jsx7(
1952
+ "ul",
1953
+ {
1954
+ "data-testid": `connector-guide-prerequisites-${entry.slug}`,
1955
+ className: "mt-1 list-disc pl-4 text-xs text-muted-foreground",
1956
+ children: prerequisites.map((item) => /* @__PURE__ */ jsx7("li", { children: item }, item))
1957
+ }
1958
+ )
1959
+ ] }),
1960
+ steps.length > 0 && /* @__PURE__ */ jsx7(
1961
+ "ol",
1962
+ {
1963
+ "data-testid": `connector-guide-steps-${entry.slug}`,
1964
+ className: "list-decimal pl-4 text-xs",
1965
+ children: steps.map((step, index) => /* @__PURE__ */ jsxs6("li", { className: "mt-1 first:mt-0", children: [
1966
+ /* @__PURE__ */ jsx7("span", { children: step.text }),
1967
+ step.copyable && /* @__PURE__ */ jsx7(
1968
+ CopyableValue,
1969
+ {
1970
+ value: step.copyable,
1971
+ testId: `connector-guide-copy-${entry.slug}-${index}`
1972
+ }
1973
+ )
1974
+ ] }, step.text))
1975
+ }
1976
+ ),
1977
+ scopeNote && /* @__PURE__ */ jsx7(
1978
+ "p",
1979
+ {
1980
+ "data-testid": `connector-guide-scope-note-${entry.slug}`,
1981
+ className: "rounded-md border border-primary/30 bg-primary/5 px-2 py-1 text-xs font-medium",
1982
+ children: scopeNote
1983
+ }
1984
+ ),
1985
+ showVerify && verify && /* @__PURE__ */ jsx7(
1986
+ "p",
1987
+ {
1988
+ "data-testid": `connector-guide-verify-${entry.slug}`,
1989
+ className: "text-xs text-muted-foreground",
1990
+ children: verify
1991
+ }
1992
+ )
1993
+ ] });
1994
+ }
1995
+ function CredentialForm({
1996
+ entry,
1997
+ fields,
1998
+ kind,
1999
+ verify,
2000
+ testSupported,
2001
+ onSubmitCredentials,
2002
+ onTestCredentials
2003
+ }) {
2004
+ const [values, setValues] = useState7(() => {
2005
+ const initial = {};
2006
+ for (const field of fields) {
2007
+ if (field.defaultValue) initial[field.key] = field.defaultValue;
2008
+ }
2009
+ return initial;
2010
+ });
2011
+ const [busy, setBusy] = useState7(false);
2012
+ const [testing, setTesting] = useState7(false);
2013
+ const [error, setError] = useState7(null);
2014
+ const [result, setResult] = useState7(null);
2015
+ const entryRef = useRef5(entry);
2016
+ entryRef.current = entry;
2017
+ const updateField = useCallback4((key, value) => {
2018
+ setValues((prev) => ({ ...prev, [key]: value }));
2019
+ setError(null);
2020
+ setResult(null);
2021
+ }, []);
2022
+ const missingRequired = fields.filter((field) => field.required !== false).some((field) => !values[field.key]?.trim());
2023
+ const failedTest = result?.valid === false;
2024
+ const untested = testSupported && result === null;
2025
+ const fieldErrors = result?.field_errors ?? null;
2026
+ const handleTest = useCallback4(async () => {
2027
+ setTesting(true);
2028
+ setError(null);
2029
+ setResult(null);
2030
+ try {
2031
+ setResult(await onTestCredentials(entryRef.current, values));
2032
+ } catch (err) {
2033
+ setError(errorMessageFrom(err, DEFAULT_CREDENTIAL_TEST_ERROR));
2034
+ } finally {
2035
+ setTesting(false);
2036
+ }
2037
+ }, [onTestCredentials, values]);
2038
+ const handleSubmit = useCallback4(async () => {
2039
+ setBusy(true);
2040
+ setError(null);
2041
+ try {
2042
+ await onSubmitCredentials(entryRef.current, values, kind);
2043
+ } catch (err) {
2044
+ setError(errorMessageFrom(err, DEFAULT_CREDENTIAL_ERROR));
2045
+ } finally {
2046
+ setBusy(false);
2047
+ }
2048
+ }, [kind, onSubmitCredentials, values]);
2049
+ return /* @__PURE__ */ jsxs6(
2050
+ "form",
2051
+ {
2052
+ "data-testid": `connector-credential-form-${entry.slug}`,
2053
+ onSubmit: (event) => {
2054
+ event.preventDefault();
2055
+ if (missingRequired || busy || testing || failedTest || untested) return;
2056
+ void handleSubmit();
2057
+ },
2058
+ className: "flex flex-col gap-2",
2059
+ children: [
2060
+ fields.map((field) => {
2061
+ const inputId = `connector-credential-${entry.slug}-${field.key}`;
2062
+ const fieldError = fieldErrors?.[field.key] ?? null;
2063
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", children: [
2064
+ /* @__PURE__ */ jsxs6("label", { htmlFor: inputId, className: "text-xs font-medium", children: [
2065
+ field.label,
2066
+ field.required !== false && /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })
2067
+ ] }),
2068
+ /* @__PURE__ */ jsx7(
2069
+ "input",
2070
+ {
2071
+ id: inputId,
2072
+ "data-testid": `connector-credential-input-${entry.slug}-${field.key}`,
2073
+ type: field.type ?? "text",
2074
+ value: values[field.key] ?? "",
2075
+ onChange: (event) => updateField(field.key, event.target.value),
2076
+ placeholder: field.placeholder ?? void 0,
2077
+ required: field.required !== false,
2078
+ disabled: busy || testing,
2079
+ autoComplete: "off",
2080
+ "aria-invalid": fieldError ? true : void 0,
2081
+ "aria-describedby": fieldError ? `${inputId}-error` : void 0,
2082
+ className: cn(
2083
+ "rounded-md border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
2084
+ fieldError ? "border-destructive" : "border-border"
2085
+ )
2086
+ }
2087
+ ),
2088
+ fieldError && /* @__PURE__ */ jsx7(
2089
+ "p",
2090
+ {
2091
+ id: `${inputId}-error`,
2092
+ "data-testid": `connector-credential-field-error-${entry.slug}-${field.key}`,
2093
+ className: "text-xs text-destructive",
2094
+ children: fieldError
2095
+ }
2096
+ )
2097
+ ] }, field.key);
2098
+ }),
2099
+ verify && /* @__PURE__ */ jsx7(
2100
+ "p",
2101
+ {
2102
+ "data-testid": `connector-guide-verify-${entry.slug}`,
2103
+ className: "text-xs text-muted-foreground",
2104
+ children: verify
2105
+ }
2106
+ ),
2107
+ result && /* @__PURE__ */ jsx7(CredentialTestResult, { entry, result }),
2108
+ error && /* @__PURE__ */ jsx7(
2109
+ "p",
2110
+ {
2111
+ role: "alert",
2112
+ "data-testid": `connector-credential-error-${entry.slug}`,
2113
+ className: "text-xs text-destructive",
2114
+ children: error
2115
+ }
2116
+ ),
2117
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-1.5", children: [
2118
+ testSupported && /* @__PURE__ */ jsx7(
2119
+ "button",
2120
+ {
2121
+ type: "button",
2122
+ "data-testid": `connector-credential-test-${entry.slug}`,
2123
+ onClick: () => void handleTest(),
2124
+ disabled: missingRequired || busy || testing,
2125
+ className: "rounded-md border border-border px-2.5 py-1 text-xs font-medium disabled:opacity-50",
2126
+ children: testing ? "Testing\u2026" : "Test connection"
2127
+ }
2128
+ ),
2129
+ /* @__PURE__ */ jsx7(
2130
+ "button",
2131
+ {
2132
+ type: "submit",
2133
+ "data-testid": `connector-credential-save-${entry.slug}`,
2134
+ disabled: missingRequired || busy || testing || failedTest || untested,
2135
+ title: failedTest ? "That credential failed its own check \u2014 fix it and test again." : untested ? "Test the credential before saving it." : void 0,
2136
+ className: "rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
2137
+ children: busy ? "Saving\u2026" : "Save credential"
2138
+ }
2139
+ )
2140
+ ] })
2141
+ ]
2142
+ }
2143
+ );
2144
+ }
2145
+ function CredentialTestResult({
2146
+ entry,
2147
+ result
2148
+ }) {
2149
+ if (result.valid === true) {
2150
+ return /* @__PURE__ */ jsxs6(
2151
+ "div",
2152
+ {
2153
+ role: "status",
2154
+ "data-testid": `connector-credential-test-pass-${entry.slug}`,
2155
+ className: "rounded-md border border-primary/30 bg-primary/5 px-2 py-1 text-xs",
2156
+ children: [
2157
+ /* @__PURE__ */ jsx7("p", { children: result.account_name ? `Verified \u2014 connected as ${result.account_name}.` : "Verified \u2014 these credentials work." }),
2158
+ result.permissions_note && /* @__PURE__ */ jsx7(
2159
+ "p",
2160
+ {
2161
+ "data-testid": `connector-credential-permissions-${entry.slug}`,
2162
+ className: "mt-0.5 font-medium",
2163
+ children: result.permissions_note
2164
+ }
2165
+ )
2166
+ ]
2167
+ }
2168
+ );
2169
+ }
2170
+ if (result.valid === false) {
2171
+ return /* @__PURE__ */ jsx7(
2172
+ "p",
2173
+ {
2174
+ role: "alert",
2175
+ "data-testid": `connector-credential-test-fail-${entry.slug}`,
2176
+ className: "rounded-md border border-destructive/30 bg-destructive/5 px-2 py-1 text-xs text-destructive",
2177
+ children: result.error || "That credential didn't pass its check."
2178
+ }
2179
+ );
2180
+ }
2181
+ return /* @__PURE__ */ jsx7(
2182
+ "p",
2183
+ {
2184
+ role: "status",
2185
+ "data-testid": `connector-credential-test-unverified-${entry.slug}`,
2186
+ className: "rounded-md border border-border px-2 py-1 text-xs text-muted-foreground",
2187
+ children: UNVERIFIABLE_CREDENTIAL_NOTE
2188
+ }
2189
+ );
2190
+ }
1766
2191
  function ManageSharing({
1767
2192
  entry,
1768
2193
  listGrants,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetreeve/ui",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Shared brand-adaptive UI primitives for MindFortress frontends — min-size tokens, a favicon→logo→monogram BrandGlyph, a responsive header that keeps the search bar usable on mobile, and AppSidebar's viewport-driven auto-collapse ladder.",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.js",
@@ -22,6 +22,11 @@
22
22
  "import": "./dist/lockup/index.mjs",
23
23
  "require": "./dist/lockup/index.js"
24
24
  },
25
+ "./console": {
26
+ "types": "./dist/console/index.d.ts",
27
+ "import": "./dist/console/index.mjs",
28
+ "require": "./dist/console/index.js"
29
+ },
25
30
  "./tokens.css": "./dist/tokens.css"
26
31
  },
27
32
  "files": [
@@ -42,6 +47,7 @@
42
47
  "access": "public"
43
48
  },
44
49
  "dependencies": {
50
+ "@base-ui/react": "^1.6.0",
45
51
  "@radix-ui/react-dropdown-menu": "^2.1.16"
46
52
  },
47
53
  "peerDependencies": {
@@ -59,11 +65,14 @@
59
65
  "@types/react": "^19",
60
66
  "@types/react-dom": "^19",
61
67
  "@vitejs/plugin-react": "^5.1.3",
68
+ "class-variance-authority": "^0.7.1",
69
+ "clsx": "^2.1.1",
62
70
  "jsdom": "^28.0.0",
63
71
  "lucide-react": "^0.400.0",
64
72
  "next": "16.1.6",
65
73
  "react": "19.2.3",
66
74
  "react-dom": "19.2.3",
75
+ "tailwind-merge": "^3.6.0",
67
76
  "tsup": "^8.0.0",
68
77
  "typescript": "5.9.3",
69
78
  "vitest": "^4.0.18",