@astryxdesign/core 0.1.6-canary.fd658d7 → 0.1.6

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": "@astryxdesign/core",
3
- "version": "0.1.6-canary.fd658d7",
3
+ "version": "0.1.6",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import {describe, it, expect, vi} from 'vitest';
13
- import {fireEvent, render, screen} from '@testing-library/react';
13
+ import {render, screen} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {Link} from './Link';
16
16
  import {LinkProvider} from './LinkProvider';
@@ -153,81 +153,9 @@ describe('Link', () => {
153
153
  Disabled Link
154
154
  </Link>,
155
155
  );
156
- // An href-less anchor has no implicit `link` role, so query by text.
157
- const link = screen.getByText('Disabled Link').closest('a');
158
- expect(link).not.toBeNull();
156
+ const link = screen.getByRole('link');
159
157
  expect(link).toHaveAttribute('aria-disabled', 'true');
160
158
  expect(link).toHaveAttribute('tabIndex', '-1');
161
- // Visual classes are preserved in the disabled state.
162
- expect(link?.className).toContain('astryx-link');
163
- });
164
-
165
- it('disabled link has no href attribute', () => {
166
- render(
167
- <Link href="/test" isDisabled>
168
- Disabled Link
169
- </Link>,
170
- );
171
- const link = screen.getByText('Disabled Link').closest('a');
172
- expect(link).not.toBeNull();
173
- expect(link).not.toHaveAttribute('href');
174
- });
175
-
176
- it('clicking a disabled link cancels default navigation', () => {
177
- render(
178
- <Link href="/test" isDisabled>
179
- Disabled Link
180
- </Link>,
181
- );
182
- const link = screen.getByText('Disabled Link').closest('a');
183
- expect(link).not.toBeNull();
184
- // fireEvent bypasses pointer-events, simulating programmatic/AT
185
- // activation. It returns false when preventDefault was called, i.e.
186
- // any default navigation behavior is cancelled.
187
- const notCancelled = fireEvent.click(link as HTMLAnchorElement);
188
- expect(notCancelled).toBe(false);
189
- });
190
-
191
- it('disabled link with onClick fires neither navigation nor the consumer onClick', () => {
192
- const handleClick = vi.fn();
193
- const {container} = render(
194
- <Link href="/test" isDisabled onClick={handleClick}>
195
- Disabled Link
196
- </Link>,
197
- );
198
- // With onClick present, a disabled Link renders as a disabled <button>
199
- // (useInteractiveRole excludes a disabled href). Either way the rendered
200
- // root must carry no live href and never invoke the consumer handler.
201
- const el = container.firstElementChild as HTMLElement;
202
- expect(el).not.toHaveAttribute('href');
203
- fireEvent.click(el);
204
- expect(handleClick).not.toHaveBeenCalled();
205
- });
206
-
207
- it('disabled link omits target and rel', () => {
208
- render(
209
- <Link href="https://example.com" isDisabled isExternalLink>
210
- Disabled External
211
- </Link>,
212
- );
213
- const link = screen.getByText('Disabled External').closest('a');
214
- expect(link).not.toBeNull();
215
- expect(link).not.toHaveAttribute('target');
216
- expect(link).not.toHaveAttribute('rel');
217
- });
218
-
219
- it('disabled link renders a plain anchor, not the custom LinkComponent', () => {
220
- render(
221
- <LinkProvider component={CustomLink}>
222
- <Link href="/custom" as={CustomLink} isDisabled>
223
- Disabled Custom
224
- </Link>
225
- </LinkProvider>,
226
- );
227
- const link = screen.getByText('Disabled Custom').closest('a');
228
- expect(link).not.toBeNull();
229
- expect(link).not.toHaveAttribute('data-custom-link');
230
- expect(link).not.toHaveAttribute('href');
231
159
  });
232
160
 
233
161
  it('renders external link with icon and target="_blank"', () => {
package/src/Link/Link.tsx CHANGED
@@ -174,10 +174,6 @@ export interface LinkProps extends BaseProps<
174
174
  hasUnderline?: boolean;
175
175
  /**
176
176
  * Whether the link is disabled.
177
- * A disabled link renders as a plain anchor without an href (and without
178
- * target/rel/onClick), so it cannot be focused or activated — no
179
- * navigation and no onClick, even via programmatic focus or assistive
180
- * technology activation.
181
177
  * @default false
182
178
  */
183
179
  isDisabled?: boolean;
@@ -265,15 +261,6 @@ export interface LinkProps extends BaseProps<
265
261
  children: ReactNode;
266
262
  }
267
263
 
268
- /**
269
- * Click handler for disabled links. The disabled anchor renders without an
270
- * href, so there is no navigation to block in practice; preventDefault is a
271
- * defensive guard against synthetic/programmatic clicks.
272
- */
273
- function preventDefaultClick(event: React.MouseEvent<HTMLAnchorElement>): void {
274
- event.preventDefault();
275
- }
276
-
277
264
  /**
278
265
  * A styled anchor link component.
279
266
  *
@@ -381,37 +368,6 @@ export function Link({
381
368
  {sharedContent}
382
369
  </button>
383
370
  );
384
- } else if (isDisabled) {
385
- // A disabled link renders as a plain <a> with no href: an href-less
386
- // anchor is not focusable and exposes no link affordance, so programmatic
387
- // focus + Enter, AT activation commands, and middle-click cannot navigate
388
- // or fire the consumer onClick. The router LinkComponent is deliberately
389
- // skipped — a disabled link performs no navigation, and custom router
390
- // links may require a live href. target/rel are omitted with the href.
391
- linkElement = (
392
- <a
393
- ref={ref as React.Ref<HTMLAnchorElement>}
394
- onClick={preventDefaultClick}
395
- aria-label={label || undefined}
396
- aria-disabled={true}
397
- tabIndex={-1}
398
- {...mergeProps(
399
- themeProps('link', {color}),
400
- stylex.props(
401
- styles.base,
402
- linkColorStyles[color],
403
- hasUnderline && styles.hasUnderline,
404
- isStandalone && styles.standalone,
405
- styles.disabled,
406
- xstyle,
407
- ),
408
- className,
409
- style,
410
- )}
411
- {...props}>
412
- {sharedContent}
413
- </a>
414
- );
415
371
  } else {
416
372
  linkElement = (
417
373
  <LinkComponent
@@ -109,7 +109,6 @@ export const docs = {
109
109
  name: 'label',
110
110
  type: 'string',
111
111
  description: 'Accessible label for the navigation landmark, applied as aria-label on the <nav> element.',
112
- default: "'Top navigation'",
113
112
  },
114
113
  {
115
114
  name: 'xstyle',
@@ -13,7 +13,6 @@ import {describe, it, expect, vi} from 'vitest';
13
13
  import {render, screen} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {TopNav} from './TopNav';
16
- import {TopNavRenderContext} from './TopNavRenderContext';
17
16
  import {TopNavHeading} from './TopNavHeading';
18
17
  import {NavIcon} from '../NavIcon';
19
18
  import {TopNavItem} from './TopNavItem';
@@ -46,35 +45,6 @@ describe('TopNav', () => {
46
45
  );
47
46
  });
48
47
 
49
- it('defaults the landmark label to "Top navigation" when label is omitted', () => {
50
- render(<TopNav />);
51
- expect(
52
- screen.getByRole('navigation', {name: 'Top navigation'}),
53
- ).toBeInTheDocument();
54
- });
55
-
56
- it('defaults the landmark label in mobile-bar mode', () => {
57
- render(
58
- <TopNavRenderContext value="mobile-bar">
59
- <TopNav heading={<span>Logo</span>} />
60
- </TopNavRenderContext>,
61
- );
62
- expect(
63
- screen.getByRole('navigation', {name: 'Top navigation'}),
64
- ).toBeInTheDocument();
65
- });
66
-
67
- it('custom label overrides the default in mobile-bar mode', () => {
68
- render(
69
- <TopNavRenderContext value="mobile-bar">
70
- <TopNav label="Utility navigation" />
71
- </TopNavRenderContext>,
72
- );
73
- expect(
74
- screen.getByRole('navigation', {name: 'Utility navigation'}),
75
- ).toBeInTheDocument();
76
- });
77
-
78
48
  it('renders heading slot content', () => {
79
49
  render(<TopNav heading={<span data-testid="title-content">Logo</span>} />);
80
50
  expect(screen.getByTestId('title-content')).toBeInTheDocument();
@@ -147,7 +147,6 @@ export interface TopNavProps extends BaseProps<HTMLElement> {
147
147
  /**
148
148
  * Accessible label for the navigation landmark.
149
149
  * Helps screen readers identify the navigation area.
150
- * @default 'Top navigation'
151
150
  */
152
151
  label?: string;
153
152
  }
@@ -177,7 +176,7 @@ export function TopNav({
177
176
  children,
178
177
  centerContent,
179
178
  endContent,
180
- label = 'Top navigation',
179
+ label,
181
180
  xstyle,
182
181
  className,
183
182
  style,
@@ -295,7 +294,9 @@ export function TopNav({
295
294
  ) : (
296
295
  endContent && (
297
296
  <div {...stylex.props(styles.endContent)}>
298
- <TopNavSlotContext value="end">{endContent}</TopNavSlotContext>
297
+ <TopNavSlotContext value="end">
298
+ {endContent}
299
+ </TopNavSlotContext>
299
300
  </div>
300
301
  )
301
302
  )}