@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 +1 -1
- package/subtab-nav.d.ts +10 -1
- package/subtab-nav.d.ts.map +1 -1
- package/subtab-nav.js +21 -4
- package/subtab-nav.js.map +1 -1
package/package.json
CHANGED
package/subtab-nav.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ export interface SubtabNavProps {
|
|
|
12
12
|
ariaLabel?: string;
|
|
13
13
|
className?: string;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
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
|
package/subtab-nav.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
/**
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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"]}
|