@igstack/app-catalog-frontend-core 0.9.0 → 0.9.1

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.
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Compact segmented toggle for the header (Apps | Service Desks). Rendered in
3
3
  * the header's `middle` slot so it adds no header height. Uses router Links so
4
- * the active segment reflects the current route and each is deep-linkable.
4
+ * each segment is deep-linkable.
5
+ *
6
+ * The active segment is derived from the current pathname rather than a plain
7
+ * exact-match on the Link: the "Apps" tab must stay active on the app-detail
8
+ * routes (`/app/<slug>`) too, since the detail panel is part of the Apps view
9
+ * (#23). A plain `activeOptions={{ exact: true }}` on `to="/"` left both tabs
10
+ * inactive on `/app/<slug>`.
5
11
  */
6
12
  export declare function ViewToggle(): import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,10 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { Link } from "@tanstack/react-router";
2
+ import { useRouterState, Link } from "@tanstack/react-router";
3
3
  import { cn } from "../../../../lib/utils.js";
4
4
  function ViewToggle() {
5
+ const pathname = useRouterState({ select: (s) => s.location.pathname });
6
+ const appsActive = pathname === "/" || pathname.startsWith("/app/");
7
+ const desksActive = pathname.startsWith("/service-desks");
5
8
  const segment = "inline-flex items-center rounded-md px-3 py-1 text-sm font-medium transition-colors";
6
9
  const active = "bg-background text-foreground shadow-sm";
7
10
  const inactive = "text-muted-foreground hover:text-foreground";
@@ -17,9 +20,8 @@ function ViewToggle() {
17
20
  {
18
21
  to: "/",
19
22
  "aria-label": "Apps",
20
- className: cn(segment, inactive),
21
- activeOptions: { exact: true },
22
- activeProps: { className: cn(segment, active) },
23
+ "aria-current": appsActive ? "page" : void 0,
24
+ className: cn(segment, appsActive ? active : inactive),
23
25
  children: "Apps"
24
26
  }
25
27
  ),
@@ -28,8 +30,8 @@ function ViewToggle() {
28
30
  {
29
31
  to: "/service-desks",
30
32
  "aria-label": "Service Desks",
31
- className: cn(segment, inactive),
32
- activeProps: { className: cn(segment, active) },
33
+ "aria-current": desksActive ? "page" : void 0,
34
+ className: cn(segment, desksActive ? active : inactive),
33
35
  children: "Service Desks"
34
36
  }
35
37
  )
@@ -1 +1 @@
1
- {"version":3,"file":"ViewToggle.js","sources":["../../../../../../src/modules/appCatalog/ui/components/ViewToggle.tsx"],"sourcesContent":["import { Link } from '@tanstack/react-router'\nimport { cn } from '~/lib/utils'\n\n/**\n * Compact segmented toggle for the header (Apps | Service Desks). Rendered in\n * the header's `middle` slot so it adds no header height. Uses router Links so\n * the active segment reflects the current route and each is deep-linkable.\n */\nexport function ViewToggle() {\n const segment =\n 'inline-flex items-center rounded-md px-3 py-1 text-sm font-medium transition-colors'\n const active = 'bg-background text-foreground shadow-sm'\n const inactive = 'text-muted-foreground hover:text-foreground'\n\n return (\n <div\n role=\"tablist\"\n aria-label=\"View\"\n className=\"inline-flex items-center gap-1 rounded-lg bg-muted p-[3px]\"\n >\n <Link\n to=\"/\"\n aria-label=\"Apps\"\n className={cn(segment, inactive)}\n activeOptions={{ exact: true }}\n activeProps={{ className: cn(segment, active) }}\n >\n Apps\n </Link>\n <Link\n to=\"/service-desks\"\n aria-label=\"Service Desks\"\n className={cn(segment, inactive)}\n activeProps={{ className: cn(segment, active) }}\n >\n Service Desks\n </Link>\n </div>\n )\n}\n"],"names":[],"mappings":";;;AAQO,SAAS,aAAa;AAC3B,QAAM,UACJ;AACF,QAAM,SAAS;AACf,QAAM,WAAW;AAEjB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAW;AAAA,MACX,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,cAAW;AAAA,YACX,WAAW,GAAG,SAAS,QAAQ;AAAA,YAC/B,eAAe,EAAE,OAAO,KAAA;AAAA,YACxB,aAAa,EAAE,WAAW,GAAG,SAAS,MAAM,EAAA;AAAA,YAC7C,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,cAAW;AAAA,YACX,WAAW,GAAG,SAAS,QAAQ;AAAA,YAC/B,aAAa,EAAE,WAAW,GAAG,SAAS,MAAM,EAAA;AAAA,YAC7C,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MAED;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"ViewToggle.js","sources":["../../../../../../src/modules/appCatalog/ui/components/ViewToggle.tsx"],"sourcesContent":["import { Link, useRouterState } from '@tanstack/react-router'\nimport { cn } from '~/lib/utils'\n\n/**\n * Compact segmented toggle for the header (Apps | Service Desks). Rendered in\n * the header's `middle` slot so it adds no header height. Uses router Links so\n * each segment is deep-linkable.\n *\n * The active segment is derived from the current pathname rather than a plain\n * exact-match on the Link: the \"Apps\" tab must stay active on the app-detail\n * routes (`/app/<slug>`) too, since the detail panel is part of the Apps view\n * (#23). A plain `activeOptions={{ exact: true }}` on `to=\"/\"` left both tabs\n * inactive on `/app/<slug>`.\n */\nexport function ViewToggle() {\n const pathname = useRouterState({ select: (s) => s.location.pathname })\n\n const appsActive = pathname === '/' || pathname.startsWith('/app/')\n const desksActive = pathname.startsWith('/service-desks')\n\n const segment =\n 'inline-flex items-center rounded-md px-3 py-1 text-sm font-medium transition-colors'\n const active = 'bg-background text-foreground shadow-sm'\n const inactive = 'text-muted-foreground hover:text-foreground'\n\n return (\n <div\n role=\"tablist\"\n aria-label=\"View\"\n className=\"inline-flex items-center gap-1 rounded-lg bg-muted p-[3px]\"\n >\n <Link\n to=\"/\"\n aria-label=\"Apps\"\n aria-current={appsActive ? 'page' : undefined}\n className={cn(segment, appsActive ? active : inactive)}\n >\n Apps\n </Link>\n <Link\n to=\"/service-desks\"\n aria-label=\"Service Desks\"\n aria-current={desksActive ? 'page' : undefined}\n className={cn(segment, desksActive ? active : inactive)}\n >\n Service Desks\n </Link>\n </div>\n )\n}\n"],"names":[],"mappings":";;;AAcO,SAAS,aAAa;AAC3B,QAAM,WAAW,eAAe,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU;AAEtE,QAAM,aAAa,aAAa,OAAO,SAAS,WAAW,OAAO;AAClE,QAAM,cAAc,SAAS,WAAW,gBAAgB;AAExD,QAAM,UACJ;AACF,QAAM,SAAS;AACf,QAAM,WAAW;AAEjB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAW;AAAA,MACX,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,cAAW;AAAA,YACX,gBAAc,aAAa,SAAS;AAAA,YACpC,WAAW,GAAG,SAAS,aAAa,SAAS,QAAQ;AAAA,YACtD,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,cAAW;AAAA,YACX,gBAAc,cAAc,SAAS;AAAA,YACrC,WAAW,GAAG,SAAS,cAAc,SAAS,QAAQ;AAAA,YACvD,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MAED;AAAA,IAAA;AAAA,EAAA;AAGN;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igstack/app-catalog-frontend-core",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Frontend core library for App Catalog",
5
5
  "homepage": "https://github.com/lislon/app-catalog",
6
6
  "repository": {
@@ -134,8 +134,8 @@
134
134
  "vite-plugin-static-copy": "^3.1.4",
135
135
  "vite-plugin-svgr": "^4.2.0",
136
136
  "vitest": "^4.1.2",
137
- "@igstack/app-catalog-backend-core": "0.9.0",
138
- "@igstack/app-catalog-shared-core": "0.9.0"
137
+ "@igstack/app-catalog-backend-core": "0.9.1",
138
+ "@igstack/app-catalog-shared-core": "0.9.1"
139
139
  },
140
140
  "peerDependencies": {
141
141
  "react": "19.1.2",
@@ -144,7 +144,7 @@
144
144
  "vite": "^6.3.5",
145
145
  "vite-plugin-svgr": "^4.2.0"
146
146
  },
147
- "gitHead": "a27e7c29dd93494100f891f47b2bc4cea4091125",
147
+ "gitHead": "69c9cf1a9802f07ead88d1d97496cbabaec5ca37",
148
148
  "scripts": {
149
149
  "build": "vite build",
150
150
  "build:lenient": "vite build --mode lenient",
@@ -123,3 +123,39 @@ describe('Service Desks view (#9)', () => {
123
123
  await waitFor(() => expect(search).toHaveFocus())
124
124
  })
125
125
  })
126
+
127
+ // #23: the header "Apps" tab must stay active while viewing an app-detail route
128
+ // (/app/<slug>) — the detail panel is part of the Apps view. Previously the Apps
129
+ // link was active only on the exact "/" route, so /app/quicksight highlighted
130
+ // neither tab.
131
+ describe('ViewToggle active tab (#23)', () => {
132
+ const appsTab = () => screen.getByRole('link', { name: 'Apps' })
133
+ const deskTab = () => screen.getByRole('link', { name: 'Service Desks' })
134
+
135
+ it('activates the Apps tab on an app-detail route (/app/$slug)', async () => {
136
+ await given(magazine.full(), { initialRoute: '/app/jira' })
137
+
138
+ await waitFor(() =>
139
+ expect(appsTab()).toHaveAttribute('aria-current', 'page'),
140
+ )
141
+ expect(deskTab()).not.toHaveAttribute('aria-current')
142
+ })
143
+
144
+ it('activates the Apps tab on the root route', async () => {
145
+ await given(magazine.full(), { initialRoute: '/' })
146
+
147
+ await waitFor(() =>
148
+ expect(appsTab()).toHaveAttribute('aria-current', 'page'),
149
+ )
150
+ expect(deskTab()).not.toHaveAttribute('aria-current')
151
+ })
152
+
153
+ it('activates only Service Desks on the /service-desks route', async () => {
154
+ await given(magazine.full(), { initialRoute: '/service-desks' })
155
+
156
+ await waitFor(() =>
157
+ expect(deskTab()).toHaveAttribute('aria-current', 'page'),
158
+ )
159
+ expect(appsTab()).not.toHaveAttribute('aria-current')
160
+ })
161
+ })
@@ -1,12 +1,23 @@
1
- import { Link } from '@tanstack/react-router'
1
+ import { Link, useRouterState } from '@tanstack/react-router'
2
2
  import { cn } from '~/lib/utils'
3
3
 
4
4
  /**
5
5
  * Compact segmented toggle for the header (Apps | Service Desks). Rendered in
6
6
  * the header's `middle` slot so it adds no header height. Uses router Links so
7
- * the active segment reflects the current route and each is deep-linkable.
7
+ * each segment is deep-linkable.
8
+ *
9
+ * The active segment is derived from the current pathname rather than a plain
10
+ * exact-match on the Link: the "Apps" tab must stay active on the app-detail
11
+ * routes (`/app/<slug>`) too, since the detail panel is part of the Apps view
12
+ * (#23). A plain `activeOptions={{ exact: true }}` on `to="/"` left both tabs
13
+ * inactive on `/app/<slug>`.
8
14
  */
9
15
  export function ViewToggle() {
16
+ const pathname = useRouterState({ select: (s) => s.location.pathname })
17
+
18
+ const appsActive = pathname === '/' || pathname.startsWith('/app/')
19
+ const desksActive = pathname.startsWith('/service-desks')
20
+
10
21
  const segment =
11
22
  'inline-flex items-center rounded-md px-3 py-1 text-sm font-medium transition-colors'
12
23
  const active = 'bg-background text-foreground shadow-sm'
@@ -21,17 +32,16 @@ export function ViewToggle() {
21
32
  <Link
22
33
  to="/"
23
34
  aria-label="Apps"
24
- className={cn(segment, inactive)}
25
- activeOptions={{ exact: true }}
26
- activeProps={{ className: cn(segment, active) }}
35
+ aria-current={appsActive ? 'page' : undefined}
36
+ className={cn(segment, appsActive ? active : inactive)}
27
37
  >
28
38
  Apps
29
39
  </Link>
30
40
  <Link
31
41
  to="/service-desks"
32
42
  aria-label="Service Desks"
33
- className={cn(segment, inactive)}
34
- activeProps={{ className: cn(segment, active) }}
43
+ aria-current={desksActive ? 'page' : undefined}
44
+ className={cn(segment, desksActive ? active : inactive)}
35
45
  >
36
46
  Service Desks
37
47
  </Link>