@braedonsaunders/appkit-ui 0.2.1 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braedonsaunders/appkit-ui",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Tokenized, animated UI primitives — the feel of the ecosystem.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
package/subtab-nav.d.ts CHANGED
@@ -12,6 +12,15 @@ export interface SubtabNavProps {
12
12
  ariaLabel?: string;
13
13
  className?: string;
14
14
  }
15
- /** Source detail/drawer subtab bar, tokenized and reusable across page shells. */
15
+ /**
16
+ * Source detail/drawer subtab bar, tokenized and reusable across page shells.
17
+ *
18
+ * The active tab carries a sliding indicator that glides between tabs on the
19
+ * same spring the `Tabs` segmented control uses — one motion system, so every
20
+ * tab bar in the suite moves the same way. The indicator is a real element in
21
+ * the static markup (not a post-hydration effect), so the active tab is marked
22
+ * before hydration; reduced-motion users get an instant swap instead of the
23
+ * glide. The call surface is unchanged: same props, same roles, same labels.
24
+ */
16
25
  export declare function SubtabNav({ tabs, active, onSelect, ariaLabel, className, }: SubtabNavProps): React.JSX.Element;
17
26
  //# sourceMappingURL=subtab-nav.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subtab-nav.d.ts","sourceRoot":"","sources":["../src/subtab-nav.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,EAAE,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,SAAsB,EACtB,SAAS,GACV,EAAE,cAAc,qBAmBhB"}
1
+ {"version":3,"file":"subtab-nav.d.ts","sourceRoot":"","sources":["../src/subtab-nav.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,EAAE,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,SAAsB,EACtB,SAAS,GACV,EAAE,cAAc,qBAiChB"}
package/subtab-nav.js CHANGED
@@ -1,11 +1,28 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
+ import { motion, useReducedMotion } from 'framer-motion';
4
5
  import { cn } from './utils.js';
5
- /** Source detail/drawer subtab bar, tokenized and reusable across page shells. */
6
+ /**
7
+ * Source detail/drawer subtab bar, tokenized and reusable across page shells.
8
+ *
9
+ * The active tab carries a sliding indicator that glides between tabs on the
10
+ * same spring the `Tabs` segmented control uses — one motion system, so every
11
+ * tab bar in the suite moves the same way. The indicator is a real element in
12
+ * the static markup (not a post-hydration effect), so the active tab is marked
13
+ * before hydration; reduced-motion users get an instant swap instead of the
14
+ * glide. The call surface is unchanged: same props, same roles, same labels.
15
+ */
6
16
  export function SubtabNav({ tabs, active, onSelect, ariaLabel = 'Sections', className, }) {
7
- return _jsx("nav", { role: "tablist", className: cn('-mb-px flex gap-1 overflow-x-auto', className), "aria-label": ariaLabel, children: tabs.map((tab) => _jsxs("button", { type: "button", role: "tab", "aria-selected": active === tab.key, disabled: tab.disabled, onClick: () => onSelect?.(tab.key), className: cn('flex shrink-0 items-center gap-2 border-b-2 px-3 py-3 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40', active === tab.key
8
- ? 'border-primary text-primary'
9
- : 'border-transparent text-fg-muted hover:border-border-strong hover:text-fg'), children: [tab.label, typeof tab.count === 'number' ? _jsx("span", { className: "rounded-full bg-bg-subtle px-1.5 text-[11px] text-fg-muted", children: tab.count }) : null] }, tab.key)) });
17
+ // Scopes the indicator's shared-layout animation to this bar, so two bars
18
+ // on one page never trade indicators with each other.
19
+ const scope = React.useId();
20
+ const reduce = useReducedMotion();
21
+ return _jsx("nav", { role: "tablist", className: cn('-mb-px flex gap-1 overflow-x-auto', className), "aria-label": ariaLabel, children: tabs.map((tab) => {
22
+ const selected = active === tab.key;
23
+ return _jsxs("button", { type: "button", role: "tab", "aria-selected": selected, disabled: tab.disabled, onClick: () => onSelect?.(tab.key), className: cn('relative flex shrink-0 items-center gap-2 border-b-2 px-3 py-3 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40', selected
24
+ ? 'border-transparent text-primary'
25
+ : 'border-transparent text-fg-muted hover:border-border-strong hover:text-fg'), children: [selected ? (_jsx(motion.span, { layoutId: `appkit-subtab-${scope}`, "aria-hidden": true, className: "absolute inset-x-2 -bottom-0.5 h-0.5 rounded-full bg-primary", transition: reduce ? { duration: 0 } : { type: 'spring', damping: 30, stiffness: 400 } })) : null, _jsxs("span", { className: "relative z-10 flex items-center gap-2", children: [tab.label, typeof tab.count === 'number' ? _jsx("span", { className: "rounded-full bg-bg-subtle px-1.5 text-[11px] text-fg-muted", children: tab.count }) : null] })] }, tab.key);
26
+ }) });
10
27
  }
11
28
  //# sourceMappingURL=subtab-nav.js.map
package/subtab-nav.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"subtab-nav.js","sourceRoot":"","sources":["../src/subtab-nav.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAiB5B,kFAAkF;AAClF,MAAM,UAAU,SAAS,CAAC,EACxB,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,SAAS,GAAG,UAAU,EACtB,SAAS,GACM;IACf,OAAO,cACL,IAAI,EAAC,SAAS,EACd,SAAS,EAAE,EAAE,CAAC,mCAAmC,EAAE,SAAS,CAAC,gBACjD,SAAS,YACrB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,kBAElB,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,KAAK,mBACK,MAAM,KAAK,GAAG,CAAC,GAAG,EACjC,QAAQ,EAAE,GAAG,CAAC,QAAQ,EACtB,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAClC,SAAS,EAAE,EAAE,CACX,6IAA6I,EAC7I,MAAM,KAAK,GAAG,CAAC,GAAG;gBAChB,CAAC,CAAC,6BAA6B;gBAC/B,CAAC,CAAC,2EAA2E,CAChF,aACD,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,4DAA4D,YAAE,GAAG,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,KAZ5I,GAAG,CAAC,GAAG,CAY+I,CAAC,GAAO,CAAA;AACvK,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { cn } from './utils'\n\nexport interface SubtabItem {\n key: string\n label: React.ReactNode\n count?: number\n disabled?: boolean\n}\n\nexport interface SubtabNavProps {\n tabs: SubtabItem[]\n active: string\n onSelect?: (key: string) => void\n ariaLabel?: string\n className?: string\n}\n\n/** Source detail/drawer subtab bar, tokenized and reusable across page shells. */\nexport function SubtabNav({\n tabs,\n active,\n onSelect,\n ariaLabel = 'Sections',\n className,\n}: SubtabNavProps) {\n return <nav\n role=\"tablist\"\n className={cn('-mb-px flex gap-1 overflow-x-auto', className)}\n aria-label={ariaLabel}\n >{tabs.map((tab) => <button\n key={tab.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={active === tab.key}\n disabled={tab.disabled}\n onClick={() => onSelect?.(tab.key)}\n className={cn(\n 'flex shrink-0 items-center gap-2 border-b-2 px-3 py-3 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40',\n active === tab.key\n ? 'border-primary text-primary'\n : 'border-transparent text-fg-muted hover:border-border-strong hover:text-fg',\n )}\n >{tab.label}{typeof tab.count === 'number' ? <span className=\"rounded-full bg-bg-subtle px-1.5 text-[11px] text-fg-muted\">{tab.count}</span> : null}</button>)}</nav>\n}\n"]}
1
+ {"version":3,"file":"subtab-nav.js","sourceRoot":"","sources":["../src/subtab-nav.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAiB5B;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,SAAS,GAAG,UAAU,EACtB,SAAS,GACM;IACf,0EAA0E;IAC1E,sDAAsD;IACtD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAA;IACjC,OAAO,cACL,IAAI,EAAC,SAAS,EACd,SAAS,EAAE,EAAE,CAAC,mCAAmC,EAAE,SAAS,CAAC,gBACjD,SAAS,YACrB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACjB,MAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,CAAC,GAAG,CAAA;YACnC,OAAO,kBAEL,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,KAAK,mBACK,QAAQ,EACvB,QAAQ,EAAE,GAAG,CAAC,QAAQ,EACtB,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAClC,SAAS,EAAE,EAAE,CACX,sJAAsJ,EACtJ,QAAQ;oBACN,CAAC,CAAC,iCAAiC;oBACnC,CAAC,CAAC,2EAA2E,CAChF,aACD,QAAQ,CAAC,CAAC,CAAC,CACT,KAAC,MAAM,CAAC,IAAI,IACV,QAAQ,EAAE,iBAAiB,KAAK,EAAE,uBAElC,SAAS,EAAC,8DAA8D,EACxE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,GACtF,CACH,CAAC,CAAC,CAAC,IAAI,EAAC,gBAAM,SAAS,EAAC,uCAAuC,aAAE,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,4DAA4D,YAAE,GAAG,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,IAAQ,KAnBtN,GAAG,CAAC,GAAG,CAmBwN,CAAA;QACxO,CAAC,CAAC,GAAO,CAAA;AACX,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport { cn } from './utils'\n\nexport interface SubtabItem {\n key: string\n label: React.ReactNode\n count?: number\n disabled?: boolean\n}\n\nexport interface SubtabNavProps {\n tabs: SubtabItem[]\n active: string\n onSelect?: (key: string) => void\n ariaLabel?: string\n className?: string\n}\n\n/**\n * Source detail/drawer subtab bar, tokenized and reusable across page shells.\n *\n * The active tab carries a sliding indicator that glides between tabs on the\n * same spring the `Tabs` segmented control uses — one motion system, so every\n * tab bar in the suite moves the same way. The indicator is a real element in\n * the static markup (not a post-hydration effect), so the active tab is marked\n * before hydration; reduced-motion users get an instant swap instead of the\n * glide. The call surface is unchanged: same props, same roles, same labels.\n */\nexport function SubtabNav({\n tabs,\n active,\n onSelect,\n ariaLabel = 'Sections',\n className,\n}: SubtabNavProps) {\n // Scopes the indicator's shared-layout animation to this bar, so two bars\n // on one page never trade indicators with each other.\n const scope = React.useId()\n const reduce = useReducedMotion()\n return <nav\n role=\"tablist\"\n className={cn('-mb-px flex gap-1 overflow-x-auto', className)}\n aria-label={ariaLabel}\n >{tabs.map((tab) => {\n const selected = active === tab.key\n return <button\n key={tab.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n disabled={tab.disabled}\n onClick={() => onSelect?.(tab.key)}\n className={cn(\n 'relative flex shrink-0 items-center gap-2 border-b-2 px-3 py-3 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40',\n selected\n ? 'border-transparent text-primary'\n : 'border-transparent text-fg-muted hover:border-border-strong hover:text-fg',\n )}\n >{selected ? (\n <motion.span\n layoutId={`appkit-subtab-${scope}`}\n aria-hidden\n className=\"absolute inset-x-2 -bottom-0.5 h-0.5 rounded-full bg-primary\"\n transition={reduce ? { duration: 0 } : { type: 'spring', damping: 30, stiffness: 400 }}\n />\n ) : null}<span className=\"relative z-10 flex items-center gap-2\">{tab.label}{typeof tab.count === 'number' ? <span className=\"rounded-full bg-bg-subtle px-1.5 text-[11px] text-fg-muted\">{tab.count}</span> : null}</span></button>\n })}</nav>\n}\n"]}