bestax-mcp 1.14.4 → 1.14.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/data/catalog.json +27 -4
- package/data/components/ConfigProvider.json +12 -3
- package/data/components/DateInput.json +1 -1
- package/data/components/DateRangeInput.json +1 -1
- package/data/components/DateTimeInput.json +1 -1
- package/data/components/Dialog.json +1 -1
- package/data/components/HoverCard.json +229 -0
- package/data/components/Modal.json +2 -2
- package/data/components/Popover.json +2 -1
- package/data/components/Portal.json +9 -5
- package/data/components/TimeInput.json +1 -1
- package/data/components/Tooltip.json +1 -1
- package/data/skills/bestax-custom-component/SKILL.md +7 -0
- package/data/skills/bestax-custom-component/references/api.md +21 -7
- package/data/skills/bestax-custom-component/references/component-catalog.md +3 -2
- package/data/skills/bestax-custom-component/references/library-contributor.md +4 -1
- package/data/skills/bestax-layout-scaffold/SKILL.md +6 -0
- package/data/skills/bestax-optimize/references/modular-build.md +2 -2
- package/data/skills/bestax-theming/references/css-variables.md +12 -1
- package/data/skills.json +5 -5
- package/package.json +2 -2
package/data/catalog.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"generatedFrom": {
|
|
4
4
|
"package": "@allxsmith/bestax-bulma",
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.29.0"
|
|
6
6
|
},
|
|
7
7
|
"docsBase": "https://bestax.io/docs",
|
|
8
8
|
"categories": [
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"Collapses",
|
|
58
58
|
"Dialog",
|
|
59
59
|
"Dropdown",
|
|
60
|
+
"HoverCard",
|
|
60
61
|
"Loading",
|
|
61
62
|
"Menu",
|
|
62
63
|
"Message",
|
|
@@ -355,7 +356,7 @@
|
|
|
355
356
|
"slug": "helpers/config",
|
|
356
357
|
"import": "import { ConfigProvider, useConfig } from '@allxsmith/bestax-bulma';",
|
|
357
358
|
"compound": false,
|
|
358
|
-
"propCount":
|
|
359
|
+
"propCount": 4,
|
|
359
360
|
"exampleCount": 0
|
|
360
361
|
},
|
|
361
362
|
{
|
|
@@ -545,6 +546,17 @@
|
|
|
545
546
|
"propCount": 21,
|
|
546
547
|
"exampleCount": 6
|
|
547
548
|
},
|
|
549
|
+
{
|
|
550
|
+
"name": "HoverCard",
|
|
551
|
+
"kind": "component",
|
|
552
|
+
"category": "components",
|
|
553
|
+
"purpose": "The `HoverCard` component shows a card previewing what's behind a link or a button while the pointer rests on it or keyboard focus is on it.",
|
|
554
|
+
"slug": "components/hovercard",
|
|
555
|
+
"import": "import { HoverCard } from '@allxsmith/bestax-bulma';",
|
|
556
|
+
"compound": false,
|
|
557
|
+
"propCount": 11,
|
|
558
|
+
"exampleCount": 6
|
|
559
|
+
},
|
|
548
560
|
{
|
|
549
561
|
"name": "Icon",
|
|
550
562
|
"kind": "component",
|
|
@@ -791,12 +803,12 @@
|
|
|
791
803
|
"name": "Portal",
|
|
792
804
|
"kind": "component",
|
|
793
805
|
"category": "helpers",
|
|
794
|
-
"purpose": "`Portal` renders its children into another part of the page, `document.body` unless `container` says otherwise, so floating content
|
|
806
|
+
"purpose": "`Portal` renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise, so floating content…",
|
|
795
807
|
"slug": "helpers/portal",
|
|
796
808
|
"import": "import { Portal } from '@allxsmith/bestax-bulma';",
|
|
797
809
|
"compound": false,
|
|
798
810
|
"propCount": 3,
|
|
799
|
-
"exampleCount":
|
|
811
|
+
"exampleCount": 4
|
|
800
812
|
},
|
|
801
813
|
{
|
|
802
814
|
"name": "Pre",
|
|
@@ -1545,6 +1557,17 @@
|
|
|
1545
1557
|
"--bulma-hero-gradient-h-offset": ["Hero"],
|
|
1546
1558
|
"--bulma-hero-gradient-l-offset": ["Hero"],
|
|
1547
1559
|
"--bulma-hero-gradient-s-offset": ["Hero"],
|
|
1560
|
+
"--bulma-hover-card-animation-duration": ["HoverCard"],
|
|
1561
|
+
"--bulma-hover-card-background": ["HoverCard"],
|
|
1562
|
+
"--bulma-hover-card-border-color": ["HoverCard"],
|
|
1563
|
+
"--bulma-hover-card-color": ["HoverCard"],
|
|
1564
|
+
"--bulma-hover-card-max-width": ["HoverCard"],
|
|
1565
|
+
"--bulma-hover-card-min-width": ["HoverCard"],
|
|
1566
|
+
"--bulma-hover-card-offset": ["HoverCard"],
|
|
1567
|
+
"--bulma-hover-card-padding": ["HoverCard"],
|
|
1568
|
+
"--bulma-hover-card-radius": ["HoverCard"],
|
|
1569
|
+
"--bulma-hover-card-shadow": ["HoverCard"],
|
|
1570
|
+
"--bulma-hover-card-z-index": ["HoverCard"],
|
|
1548
1571
|
"--bulma-icon-dimensions": ["Icon", "IconText"],
|
|
1549
1572
|
"--bulma-icon-dimensions-large": ["Icon", "IconText"],
|
|
1550
1573
|
"--bulma-icon-dimensions-medium": ["Icon", "IconText"],
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"accessibility": null,
|
|
9
9
|
"related": [],
|
|
10
10
|
"storybook": null,
|
|
11
|
-
"summary": "ConfigProvider injects configuration into all child components via React context.",
|
|
11
|
+
"summary": "ConfigProvider injects configuration into all child components via React context. A nested provider replaces the outer one's settings rather than adding to them, so inside it any setting it leaves out is back to its default.",
|
|
12
12
|
"import": "import { ConfigProvider, useConfig } from '@allxsmith/bestax-bulma';",
|
|
13
13
|
"sourceFile": "bulma-ui/src/helpers/Config.tsx",
|
|
14
14
|
"rootClass": null,
|
|
15
15
|
"parts": [
|
|
16
16
|
{
|
|
17
17
|
"path": "ConfigProvider",
|
|
18
|
-
"summary": "ConfigProvider injects configuration into all child components via React context.",
|
|
18
|
+
"summary": "ConfigProvider injects configuration into all child components via React context. A nested provider replaces the outer one's settings rather than adding to them, so inside it any setting it leaves out is back to its default.",
|
|
19
19
|
"component": null,
|
|
20
20
|
"props": [
|
|
21
21
|
{
|
|
@@ -44,6 +44,15 @@
|
|
|
44
44
|
"required": false,
|
|
45
45
|
"inherited": false,
|
|
46
46
|
"deprecated": false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "portalContainer",
|
|
50
|
+
"type": "string | HTMLElement",
|
|
51
|
+
"default": null,
|
|
52
|
+
"description": "Where portaled content goes when it has no target of its own: what a `Portal` without a `container` renders, and what the library's components portal, such as Popover's `appendToBody` panel, a `Modal` with `portal` set to `true`, or a `Toast`. An element, or a `document.querySelector` selector. Unset, or a selector that matches nothing, that content goes to `document.body`. A target a component is given, such as a `Toast`'s `container`, still wins, and nothing portals that would otherwise render in place. The overlays and panels this moves are `position: fixed`, so pick an element with no ancestor that has a `transform`, `filter`, `backdrop-filter`, `perspective`, `will-change: transform`, or a `contain` that includes `paint` or `layout`. That ancestor becomes their containing block, and they cover or line up against it instead of the viewport. An ancestor that starts a stacking context, with a `z-index` or `isolation: isolate`, keeps them inside it, under anything the page stacks above it. `NotificationContainer`'s status region moves too, so this also decides where its announcements come from, and an `aria-hidden` ancestor silences them. A selector can't find an element inside a shadow root, so pass the element itself there, held in state from a ref callback. Content that portals before the element is set goes to `document.body` and moves when it is, which remounts it, as `Portal`'s `container` describes.",
|
|
53
|
+
"required": false,
|
|
54
|
+
"inherited": false,
|
|
55
|
+
"deprecated": false
|
|
47
56
|
}
|
|
48
57
|
],
|
|
49
58
|
"extraProps": [],
|
|
@@ -52,5 +61,5 @@
|
|
|
52
61
|
}
|
|
53
62
|
],
|
|
54
63
|
"cssVars": [],
|
|
55
|
-
"doc": "# ConfigProvider\n\n## Overview\n\nThe `ConfigProvider` component provides a React context for configuring global settings across all Bulma UI components. Currently, it supports setting a `classPrefix` that will be automatically applied to **all Bulma CSS classes**, including both main component classes (like `button`, `box`, `title`) and helper/modifier classes (like `is-primary`, `has-text-centered`, `m-4`), allowing you to namespace Bulma classes to avoid conflicts with other CSS frameworks or customize the class naming convention.\n\n---\n\n## Import\n\n```tsx\nimport { ConfigProvider, useConfig } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Hooks\n\n### useConfig\n\nReturns the current configuration context values.\n\n```tsx\nconst { classPrefix } = useConfig();\n```\n\n### useClassPrefix\n\nConvenience hook that returns just the classPrefix string (or empty string if not set).\n\n```tsx\nconst classPrefix = useClassPrefix();\n```\n\n---\n\n## Usage Examples\n\n### Basic Usage with Class Prefix\n\nUse `ConfigProvider` to add a prefix to all Bulma CSS classes within its scope:\n\n```tsx\nimport {\n ConfigProvider,\n Box,\n Title,\n Button,\n Notification,\n} from '@allxsmith/bestax-bulma';\n\nfunction App() {\n return (\n <div>\n <Title size=\"3\">Prefixed Bulma Components</Title>\n <p>\n This demonstrates using ConfigProvider with{' '}\n <code>classPrefix=\"bestax-\"</code> to add the \"bestax-\" prefix to all\n component CSS classes. This is useful when integrating Bulma with other\n CSS frameworks or when you need to namespace Bulma classes to avoid\n conflicts.\n </p>\n\n <Notification color=\"info\" mt=\"4\">\n <strong>Tip:</strong> Open your browser's developer tools and inspect\n the DOM elements below. You'll see that all Bulma CSS classes have the\n \"bestax-\" prefix applied.\n </Notification>\n\n <ConfigProvider classPrefix=\"bestax-\">\n <Box mt=\"4\" p=\"4\">\n {/* \n This Box will have classes:\n - bestax-box (main class)\n - bestax-mt-4 (helper class)\n - bestax-p-4 (helper class)\n */}\n <Title size=\"4\">Prefixed Components</Title>\n {/* \n This Title will have classes:\n - bestax-title (main class)\n - bestax-is-4 (modifier class)\n */}\n <p>\n All components inside this ConfigProvider will have their CSS\n classes prefixed with \"bestax-\". This allows you to use Bulma\n alongside other CSS frameworks without class name conflicts.\n </p>\n <Button color=\"primary\" mt=\"3\" isRounded>\n {/* \n This Button will have classes:\n - bestax-button (main class)\n - bestax-is-primary (modifier class)\n - bestax-is-rounded (modifier class)\n - bestax-mt-3 (helper class)\n */}\n Bulma-Prefixed Button\n </Button>\n </Box>\n </ConfigProvider>\n\n <Box mt=\"4\" p=\"4\">\n <Title size=\"4\">Standard Components (No Prefix)</Title>\n <p>\n Components outside the ConfigProvider use standard Bulma classes\n without any prefix.\n </p>\n <Button color=\"info\">Standard Button</Button>\n </Box>\n </div>\n );\n}\n```\n\n### Nested ConfigProviders\n\nConfigProviders can be nested, with inner providers overriding outer settings:\n\n```tsx\nfunction NestedExample() {\n return (\n <ConfigProvider classPrefix=\"outer-\">\n <Box p=\"4\">\n <Title>Outer Prefix (outer-)</Title>\n\n <ConfigProvider classPrefix=\"inner-\">\n <Box p=\"4\" mt=\"3\">\n <Title>Inner Prefix (inner-)</Title>\n <Button color=\"primary\">Inner Button</Button>\n </Box>\n </ConfigProvider>\n\n <Button color=\"info\" mt=\"3\">\n Outer Button\n </Button>\n </Box>\n </ConfigProvider>\n );\n}\n```\n\n### Using Configuration in Custom Components\n\nAccess the configuration in your own components using the `useConfig` hook:\n\n```tsx\nimport { useConfig } from '@allxsmith/bestax-bulma';\n\nfunction CustomComponent() {\n const { classPrefix } = useConfig();\n const className = classPrefix\n ? `${classPrefix}custom-component`\n : 'custom-component';\n\n return (\n <div className={className}>\n My custom component with prefix: {classPrefix || 'none'}\n </div>\n );\n}\n```\n\n---\n\n## Use Cases\n\n### Framework Integration\n\nWhen integrating Bulma with other CSS frameworks like Bootstrap or Tailwind CSS:\n\n```tsx\n<ConfigProvider classPrefix=\"bestax-\">\n <App />\n</ConfigProvider>\n```\n\nThis ensures all Bulma classes are prefixed with `bestax-`, preventing conflicts with other framework classes.\n\n### Multi-tenant Applications\n\nFor applications that need to support different styling themes or brands:\n\n```tsx\n<ConfigProvider classPrefix={`${tenantName}-`}>\n <TenantApp />\n</ConfigProvider>\n```\n\n### Component Library Wrapping\n\nWhen wrapping Bulma UI components in your own component library:\n\n```tsx\n<ConfigProvider classPrefix=\"mylib-\">\n <MyComponentLibrary />\n</ConfigProvider>\n```\n\n---\n\n## Notes\n\n- The `classPrefix` is applied to **all Bulma classes**:\n - Main component classes (e.g., `button` → `bestax-button`, `box` → `bestax-box`)\n - Helper/modifier classes (e.g., `is-primary` → `bestax-is-primary`, `has-text-centered` → `bestax-has-text-centered`)\n - Spacing classes (e.g., `m-4` → `bestax-m-4`, `p-2` → `bestax-p-2`)\n - All other Bulma utility classes\n- Changes to the `classPrefix` will affect all child components immediately\n- If no `classPrefix` is provided, components use standard Bulma class names\n- The ConfigProvider must wrap all components that should inherit the configuration\n\n---\n\n## TypeScript Support\n\nFull TypeScript support is included:\n\n```tsx\ninterface ConfigContextProps {\n classPrefix?: string;\n iconLibrary?: 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n}\n\ninterface ConfigProviderProps {\n children: ReactNode;\n classPrefix?: string;\n iconLibrary?: 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n}\n```\n\n---\n\n## Props\n\n| Prop | Type | Description |\n| ------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |\n| `children` | `ReactNode` | The child components that will inherit the configuration settings. |\n| `classPrefix` | `string` | Optional prefix to add to all Bulma CSS classes. Useful for namespacing when integrating with other frameworks. |\n| `iconLibrary` | `'fa' \\| 'mdi' \\| 'ion' \\| 'material-icons' \\| 'material-symbols'` | Default icon library for all child components. |"
|
|
64
|
+
"doc": "# ConfigProvider\n\n## Overview\n\nThe `ConfigProvider` component provides a React context for configuring global settings across all Bulma UI components. Its `classPrefix` is applied automatically to **all Bulma CSS classes**, including both main component classes (like `button`, `box`, `title`) and helper/modifier classes (like `is-primary`, `has-text-centered`, `m-4`), allowing you to namespace Bulma classes to avoid conflicts with other CSS frameworks or customize the class naming convention.\n\n---\n\n## Import\n\n```tsx\nimport { ConfigProvider, useConfig } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Hooks\n\n### useConfig\n\nReturns the current configuration context values.\n\n```tsx\nconst { classPrefix } = useConfig();\n```\n\n### useClassPrefix\n\nConvenience hook that returns just the classPrefix string (or empty string if not set).\n\n```tsx\nconst classPrefix = useClassPrefix();\n```\n\n---\n\n## Usage Examples\n\n### Basic Usage with Class Prefix\n\nUse `ConfigProvider` to add a prefix to all Bulma CSS classes within its scope:\n\n```tsx\nimport {\n ConfigProvider,\n Box,\n Title,\n Button,\n Notification,\n} from '@allxsmith/bestax-bulma';\n\nfunction App() {\n return (\n <div>\n <Title size=\"3\">Prefixed Bulma Components</Title>\n <p>\n This demonstrates using ConfigProvider with{' '}\n <code>classPrefix=\"bestax-\"</code> to add the \"bestax-\" prefix to all\n component CSS classes. This is useful when integrating Bulma with other\n CSS frameworks or when you need to namespace Bulma classes to avoid\n conflicts.\n </p>\n\n <Notification color=\"info\" mt=\"4\">\n <strong>Tip:</strong> Open your browser's developer tools and inspect\n the DOM elements below. You'll see that all Bulma CSS classes have the\n \"bestax-\" prefix applied.\n </Notification>\n\n <ConfigProvider classPrefix=\"bestax-\">\n <Box mt=\"4\" p=\"4\">\n {/* \n This Box will have classes:\n - bestax-box (main class)\n - bestax-mt-4 (helper class)\n - bestax-p-4 (helper class)\n */}\n <Title size=\"4\">Prefixed Components</Title>\n {/* \n This Title will have classes:\n - bestax-title (main class)\n - bestax-is-4 (modifier class)\n */}\n <p>\n All components inside this ConfigProvider will have their CSS\n classes prefixed with \"bestax-\". This allows you to use Bulma\n alongside other CSS frameworks without class name conflicts.\n </p>\n <Button color=\"primary\" mt=\"3\" isRounded>\n {/* \n This Button will have classes:\n - bestax-button (main class)\n - bestax-is-primary (modifier class)\n - bestax-is-rounded (modifier class)\n - bestax-mt-3 (helper class)\n */}\n Bulma-Prefixed Button\n </Button>\n </Box>\n </ConfigProvider>\n\n <Box mt=\"4\" p=\"4\">\n <Title size=\"4\">Standard Components (No Prefix)</Title>\n <p>\n Components outside the ConfigProvider use standard Bulma classes\n without any prefix.\n </p>\n <Button color=\"info\">Standard Button</Button>\n </Box>\n </div>\n );\n}\n```\n\n### Nested ConfigProviders\n\nConfigProviders can be nested. An inner provider replaces the outer one's settings rather than adding to them, so inside it any setting it leaves out is back to its default:\n\n```tsx\nfunction NestedExample() {\n return (\n <ConfigProvider classPrefix=\"outer-\">\n <Box p=\"4\">\n <Title>Outer Prefix (outer-)</Title>\n\n <ConfigProvider classPrefix=\"inner-\">\n <Box p=\"4\" mt=\"3\">\n <Title>Inner Prefix (inner-)</Title>\n <Button color=\"primary\">Inner Button</Button>\n </Box>\n </ConfigProvider>\n\n <Button color=\"info\" mt=\"3\">\n Outer Button\n </Button>\n </Box>\n </ConfigProvider>\n );\n}\n```\n\n### Where Portaled Content Goes\n\n`portalContainer` sets where portaled content goes when it has no target of its own. That covers a [`Portal`](https://bestax.io/docs/api/helpers/portal) with no `container`, and what the library's components portal, such as the panel `Popover` renders with `appendToBody`, a `Modal` or `Dialog` with `portal` set to `true`, a `Toast`, or a `Sidebar`. It takes an element or a `document.querySelector` selector. Unset, that content goes to `document.body`.\n\nA target a component is given still wins, such as a `Modal`'s `portal` selector or a `Toast`'s `container`. And `portalContainer` only changes where portaled content goes: a component that renders in place, like a `Modal` without `portal` or a `Toast` with `inline`, still does.\n\n`document.body` is outside any styles scoped to where the content was declared: a scoped `Theme`'s variables, or a shadow root's stylesheets. To keep them, render an element inside that area and make it the `portalContainer`, held in state from a ref callback. Below, the panel keeps the purple primary its `Theme` sets, which it would lose at the end of the page:\n\n```tsx live\nfunction ThemedOverlays() {\n const [overlays, setOverlays] = useState<HTMLElement | null>(null);\n\n return (\n <Theme primaryH=\"270\" primaryS=\"100%\" primaryL=\"50%\">\n <ConfigProvider portalContainer={overlays ?? undefined}>\n <Popover\n appendToBody\n ariaLabel=\"Themed panel\"\n trigger={<Button color=\"primary\">Open</Button>}\n >\n <Popover.Body>\n <Button color=\"primary\">Still purple</Button>\n </Popover.Body>\n </Popover>\n </ConfigProvider>\n <div ref={setOverlays} />\n </Theme>\n );\n}\n```\n\nA selector can't find an element inside a shadow root, since `document.querySelector` doesn't look there, so in a shadow root pass the element itself, as above. Content that portals before the element is set goes to `document.body` and moves when it is, which remounts it and resets its state, as [`Portal`'s `container`](https://bestax.io/docs/api/helpers/portal#into-a-container) describes. A nested provider like this one needs the class prefix and icon library repeated if your app sets them (see [Nested ConfigProviders](https://bestax.io/docs/api/helpers/config#nested-configproviders)).\n\nWhat gets portaled is `position: fixed`, so put the element where no ancestor has a `transform`, `filter`, `backdrop-filter`, `perspective`, `will-change: transform`, or a `contain` that includes `paint` or `layout`. Any of those becomes the containing block for the overlays and panels, which then cover or line up against that ancestor instead of the viewport. An ancestor that starts a stacking context, with a `z-index` or `isolation: isolate`, keeps them inside it, under anything the page stacks above it.\n\n`NotificationContainer` portals its status region along with its notifications, so the `portalContainer` also decides where announcements come from. Keep it out of any `aria-hidden` ancestor, which takes the region out of the accessibility tree and silences them.\n\n### Using Configuration in Custom Components\n\nAccess the configuration in your own components using the `useConfig` hook:\n\n```tsx\nimport { useConfig } from '@allxsmith/bestax-bulma';\n\nfunction CustomComponent() {\n const { classPrefix } = useConfig();\n const className = classPrefix\n ? `${classPrefix}custom-component`\n : 'custom-component';\n\n return (\n <div className={className}>\n My custom component with prefix: {classPrefix || 'none'}\n </div>\n );\n}\n```\n\n---\n\n## Use Cases\n\n### Framework Integration\n\nWhen integrating Bulma with other CSS frameworks like Bootstrap or Tailwind CSS:\n\n```tsx\n<ConfigProvider classPrefix=\"bestax-\">\n <App />\n</ConfigProvider>\n```\n\nThis ensures all Bulma classes are prefixed with `bestax-`, preventing conflicts with other framework classes.\n\n### Multi-tenant Applications\n\nFor applications that need to support different styling themes or brands:\n\n```tsx\n<ConfigProvider classPrefix={`${tenantName}-`}>\n <TenantApp />\n</ConfigProvider>\n```\n\n### Component Library Wrapping\n\nWhen wrapping Bulma UI components in your own component library:\n\n```tsx\n<ConfigProvider classPrefix=\"mylib-\">\n <MyComponentLibrary />\n</ConfigProvider>\n```\n\n---\n\n## Notes\n\n- The `classPrefix` is applied to **all Bulma classes**:\n - Main component classes (e.g., `button` → `bestax-button`, `box` → `bestax-box`)\n - Helper/modifier classes (e.g., `is-primary` → `bestax-is-primary`, `has-text-centered` → `bestax-has-text-centered`)\n - Spacing classes (e.g., `m-4` → `bestax-m-4`, `p-2` → `bestax-p-2`)\n - All other Bulma utility classes\n- Changes to the `classPrefix` will affect all child components immediately\n- If no `classPrefix` is provided, components use standard Bulma class names\n- The ConfigProvider must wrap all components that should inherit the configuration\n\n---\n\n## TypeScript Support\n\nFull TypeScript support is included:\n\n```tsx\ninterface ConfigContextProps {\n classPrefix?: string;\n iconLibrary?: 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n portalContainer?: string | HTMLElement;\n}\n\ninterface ConfigProviderProps {\n children: ReactNode;\n classPrefix?: string;\n iconLibrary?: 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n portalContainer?: string | HTMLElement;\n}\n```\n\n---\n\n## Props\n\n| Prop | Type | Description |\n| ----------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `children` | `ReactNode` | The child components that will inherit the configuration settings. |\n| `classPrefix` | `string` | Optional prefix to add to all Bulma CSS classes. Useful for namespacing when integrating with other frameworks. |\n| `iconLibrary` | `'fa' \\| 'mdi' \\| 'ion' \\| 'material-icons' \\| 'material-symbols'` | Default icon library for all child components. |\n| `portalContainer` | `string` \\| `HTMLElement` | Where portaled content goes when it has no target of its own: what a `Portal` without a `container` renders, and what the library's components portal, such as `Popover`'s `appendToBody` panel or a `Modal` with `portal`. An element, or a `document.querySelector` selector. Unset, or a selector that matches nothing, it goes to `document.body`. A target a component is given still wins. |"
|
|
56
65
|
}
|
|
@@ -551,7 +551,7 @@
|
|
|
551
551
|
"name": "appendToBody",
|
|
552
552
|
"type": "boolean",
|
|
553
553
|
"default": "false",
|
|
554
|
-
"description": "Render the popover into `document.body`
|
|
554
|
+
"description": "Render the popover through a portal, into the `portalContainer` a `ConfigProvider` sets, `document.body` by default.",
|
|
555
555
|
"required": false,
|
|
556
556
|
"inherited": true,
|
|
557
557
|
"deprecated": false
|
|
@@ -414,7 +414,7 @@
|
|
|
414
414
|
"name": "appendToBody",
|
|
415
415
|
"type": "boolean",
|
|
416
416
|
"default": "false",
|
|
417
|
-
"description": "Render the popover into `document.body`
|
|
417
|
+
"description": "Render the popover through a portal, into the `portalContainer` a `ConfigProvider` sets, `document.body` by default.",
|
|
418
418
|
"required": false,
|
|
419
419
|
"inherited": true,
|
|
420
420
|
"deprecated": false
|
|
@@ -538,7 +538,7 @@
|
|
|
538
538
|
"name": "appendToBody",
|
|
539
539
|
"type": "boolean",
|
|
540
540
|
"default": "false",
|
|
541
|
-
"description": "Render the popover into `document.body`
|
|
541
|
+
"description": "Render the popover through a portal, into the `portalContainer` a `ConfigProvider` sets, `document.body` by default.",
|
|
542
542
|
"required": false,
|
|
543
543
|
"inherited": true,
|
|
544
544
|
"deprecated": false
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"title": "Portal",
|
|
30
|
-
"code": "function example() {\n const [showDialog, setShowDialog] = useState(false);\n return (\n <Block>\n <Button onClick={() => setShowDialog(true)}>Show Portaled Dialog</Button>\n <Dialog\n isOpen={showDialog}\n title=\"
|
|
30
|
+
"code": "function example() {\n const [showDialog, setShowDialog] = useState(false);\n return (\n <Block>\n <Button onClick={() => setShowDialog(true)}>Show Portaled Dialog</Button>\n <Dialog\n isOpen={showDialog}\n title=\"Portaled Dialog\"\n message=\"This dialog is portaled, so it escapes any ancestor with overflow: hidden.\"\n onConfirm={() => setShowDialog(false)}\n showCancel={false}\n portal\n />\n </Block>\n );\n}"
|
|
31
31
|
}
|
|
32
32
|
],
|
|
33
33
|
"accessibility": "- Uses `role=\"alertdialog\"` for proper screen reader announcement\n- Has `aria-modal=\"true\"` to indicate modal behavior — note this does not make background content inert; no `inert` attribute is applied, so background content stays reachable by pointer\n- `title` is wired up as `aria-labelledby` and `message` as `aria-describedby`\n- Focus moves to the confirm (or cancel, with `focusCancel`) button when opened\n- Escape key closes the dialog (when `canCancel` is true) — handled by the underlying [`Modal`](https://bestax.io/docs/api/components/modal), which routes it to the topmost open modal only\n- Tab and Shift+Tab cycle within the dialog while it is open — also from the underlying [`Modal`](https://bestax.io/docs/api/components/modal)\n- Body scroll is prevented when dialog is open, through the ref-counted lock shared with `Modal`, `Sidebar` and `Loading`\n- Confirm/cancel buttons are keyboard accessible",
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "HoverCard",
|
|
3
|
+
"kind": "component",
|
|
4
|
+
"category": "components",
|
|
5
|
+
"slug": "components/hovercard",
|
|
6
|
+
"docsUrl": "https://bestax.io/docs/api/components/hovercard",
|
|
7
|
+
"examples": [
|
|
8
|
+
{
|
|
9
|
+
"title": "Basic HoverCard",
|
|
10
|
+
"code": "<HoverCard trigger={<Link href=\"#ada\">Ada Lovelace</Link>}>\n <Media>\n <Media.Left>\n <Avatar name=\"Ada Lovelace\" size=\"48x48\" />\n </Media.Left>\n <Media.Content>\n <Title as=\"p\" size=\"6\">\n Ada Lovelace\n </Title>\n <SubTitle as=\"p\" size=\"6\">\n Analytical Engine team\n </SubTitle>\n <Link href=\"#profile\">View profile</Link>\n </Media.Content>\n </Media>\n</HoverCard>"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"title": "In Running Text",
|
|
14
|
+
"code": "<Paragraph>\n Reviewed by{' '}\n <HoverCard appendToBody trigger={<Link href=\"#ada\">Ada Lovelace</Link>}>\n <Title as=\"p\" size=\"6\">\n Ada Lovelace\n </Title>\n <SubTitle as=\"p\" size=\"6\">\n Analytical Engine team\n </SubTitle>\n <Link href=\"#profile\">View profile</Link>\n </HoverCard>{' '}\n and merged the same day.\n</Paragraph>"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"title": "Placement",
|
|
18
|
+
"code": "<Buttons>\n <HoverCard\n openOnTap\n position=\"bottom-left\"\n trigger={<Button>bottom-left</Button>}\n >\n <Paragraph>Opens below, on the left edge.</Paragraph>\n </HoverCard>\n <HoverCard\n openOnTap\n position=\"bottom-right\"\n trigger={<Button>bottom-right</Button>}\n >\n <Paragraph>Opens below, on the right edge.</Paragraph>\n </HoverCard>\n <HoverCard\n openOnTap\n position=\"auto\"\n trigger={<Button color=\"info\">auto</Button>}\n >\n <Paragraph>Opens wherever it fits.</Paragraph>\n </HoverCard>\n</Buttons>"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"title": "Delays",
|
|
22
|
+
"code": "<Buttons>\n <HoverCard openOnTap openDelay={0} trigger={<Button>Opens at once</Button>}>\n <Paragraph>No wait before this one.</Paragraph>\n </HoverCard>\n <HoverCard\n openOnTap\n openDelay={1000}\n closeDelay={600}\n trigger={<Button>Takes its time</Button>}\n >\n <Paragraph>A slower card, with longer to reach it.</Paragraph>\n </HoverCard>\n</Buttons>"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"title": "On Touch Screens",
|
|
26
|
+
"code": "<HoverCard openOnTap trigger={<Button>Engine team</Button>}>\n <Title as=\"p\" size=\"6\">\n Analytical Engine team\n </Title>\n <Paragraph mb=\"2\">Ada Lovelace and Charles Babbage</Paragraph>\n <Link href=\"#team\">View the team</Link>\n</HoverCard>"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"title": "Controlled",
|
|
30
|
+
"code": "function example() {\n const [open, setOpen] = useState(false);\n const [following, setFollowing] = useState(false);\n\n return (\n <>\n <Paragraph mb=\"3\">\n The card is {open ? 'open' : 'closed'}\n {following ? ', and you follow Ada' : ''}.\n </Paragraph>\n <HoverCard\n open={open}\n onOpenChange={setOpen}\n trigger={<Link href=\"#ada\">Ada Lovelace</Link>}\n >\n <Paragraph mb=\"3\">Analytical Engine team</Paragraph>\n <Button\n size=\"small\"\n color=\"primary\"\n onClick={() => {\n setFollowing(true);\n setOpen(false);\n }}\n >\n Follow\n </Button>\n </HoverCard>\n </>\n );\n}"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"accessibility": "`HoverCard` is built to\n[WCAG's Content on Hover or Focus](https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus.html)\ncriterion, condition by condition:\n\n- **Dismissible:** <kbd>Escape</kbd> closes the card wherever focus is, without moving the pointer\n or focus. The keypress belongs to whatever has focus. When that's the card, its trigger or\n nothing, the card takes it, so a `Popover` or `Modal` around it stays open. When it's something\n else, such as a field elsewhere on the page or a `Modal` opened from the card, the card still\n closes and the key goes on to it. With one card inside another, the inner one closes first.\n Focus that was in the card goes back to the trigger, and the card stays closed until the pointer\n or focus leaves and comes back.\n- **Hoverable:** the pointer can move from the trigger onto the card. `closeDelay` covers the gap\n between them, and the card takes pointer events, unlike a tooltip.\n- **Persistent:** no timer closes a card that's hovered or focused. Focus inside the card holds it\n however it got there. Whether a click focuses what it lands on is up to the browser, though:\n Safari and Firefox on macOS don't focus a button on click, so there a clicked button doesn't keep\n the card open once the pointer moves away.\n\nAnd around it:\n\n- Keyboard focus on the trigger opens the card, as hover does. Focus that a press brings doesn't,\n and neither does a touch pointer, so tapping the trigger on a phone just does what the trigger\n does. Clicking the trigger never toggles the card.\n- By default, then, a phone or tablet never opens the card, so whatever it shows has to be\n reachable from the trigger as well. `openOnTap` opens it with a tap, for a trigger with no tap\n action of its own. While a tap holds it open, a tap outside the trigger and the card closes it.\n- The trigger gets `aria-expanded` and `aria-controls`, which say it's expanded and name the card\n only while the card is on the page. It gets no role, no tab stop and no `aria-haspopup`, so the trigger element itself has to\n take focus, such as a link or a button. A link inside a `<span>` doesn't do, since focus comes\n back to the trigger and its ARIA is on it. A development build warns when the trigger can't take\n focus.\n- The card is a plain element with an `id` and no role. It isn't a dialog, since focus doesn't move\n into it, and it isn't a tooltip, since it can hold links. In place, a screen reader meets it\n right after the trigger, Tab goes from the trigger into its links and buttons, and tabbing past\n them closes it.\n- With `appendToBody` the card sits in the Tab order where its container is. By default that's\n the end of `<body>`, so it comes last: Tab reaches it only after the rest of the page, and focus\n leaving the trigger on the way closes it. Keyboard users can open it, read it and close it, so\n whatever it links to should be reachable from the page as well.\n- Opening the card moves no focus, and nothing traps it. Links and buttons belong in a card; a form\n belongs in a `Popover`, which opens on purpose and holds focus.\n- The fade-in is turned off under `prefers-reduced-motion: reduce`.",
|
|
34
|
+
"related": ["Media", "Popover", "Portal", "Tooltip", "useBulmaClasses"],
|
|
35
|
+
"storybook": "https://bestax.io/storybook/?path=/story/components-hovercard--default",
|
|
36
|
+
"summary": "The `HoverCard` component shows a card previewing what's behind a link or a button while the pointer rests on it or keyboard focus is on it. Nothing moves focus when the card opens, and nothing traps it. In place, the card comes right after the trigger, so Tab goes from the trigger into its links and tabbing past them closes it. The pointer can move onto the card, Escape closes it from anywhere, and by default touch doesn't open it, so tapping the trigger just does what the trigger does. `openOnTap` opens it with a tap, for a trigger with no tap action of its own. For hints with nothing to click use `Tooltip`, and for content opened on purpose, such as a form, use `Popover`.",
|
|
37
|
+
"import": "import { HoverCard } from '@allxsmith/bestax-bulma';",
|
|
38
|
+
"sourceFile": "bulma-ui/src/components/HoverCard.tsx",
|
|
39
|
+
"rootClass": "hover-card",
|
|
40
|
+
"parts": [
|
|
41
|
+
{
|
|
42
|
+
"path": "HoverCard",
|
|
43
|
+
"summary": "The `HoverCard` component shows a card previewing what's behind a link or a button while the pointer rests on it or keyboard focus is on it. Nothing moves focus when the card opens, and nothing traps it. In place, the card comes right after the trigger, so Tab goes from the trigger into its links and tabbing past them closes it. The pointer can move onto the card, Escape closes it from anywhere, and by default touch doesn't open it, so tapping the trigger just does what the trigger does. `openOnTap` opens it with a tap, for a trigger with no tap action of its own. For hints with nothing to click use `Tooltip`, and for content opened on purpose, such as a form, use `Popover`.",
|
|
44
|
+
"component": null,
|
|
45
|
+
"props": [
|
|
46
|
+
{
|
|
47
|
+
"name": "trigger",
|
|
48
|
+
"type": "React.ReactElement",
|
|
49
|
+
"default": null,
|
|
50
|
+
"description": "The element the card previews, such as a link or a `Button`. The element itself has to take focus, so keyboard users can open the card too: a link inside a `<span>` doesn't do, since focus comes back to the trigger and its ARIA is on it. HoverCard gives it no role and no tab stop, since it can't know what the element is, and a development build warns when it can't take focus: use a link or a button, or give the element `tabIndex={0}`. A disabled button can't take focus either. HoverCard adds `aria-expanded` and `aria-controls`, both naming the card only while it is on the page, so it has to be a single element that passes those to its DOM node. Its own click and keys stay its own, and the card doesn't toggle on them, except Escape while the card is open, which closes the card before the trigger sees it.",
|
|
51
|
+
"required": false,
|
|
52
|
+
"inherited": false,
|
|
53
|
+
"deprecated": false
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "children",
|
|
57
|
+
"type": "React.ReactNode",
|
|
58
|
+
"default": null,
|
|
59
|
+
"description": "The card's content: a preview whose links and buttons are fine, but not a form, which belongs in a `Popover`. The card pads itself, so content goes straight in.",
|
|
60
|
+
"required": false,
|
|
61
|
+
"inherited": false,
|
|
62
|
+
"deprecated": false
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "open",
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": null,
|
|
68
|
+
"description": "Controlled open state. When set, hover, focus and Escape only report through `onOpenChange`, so an action inside the card can close it. While the card is open, Escape is reported, and when focus is in the card, on its trigger or nowhere, the key stops there even if you keep the card open, so a `Modal` around it doesn't close from the keyboard until the card does.",
|
|
69
|
+
"required": false,
|
|
70
|
+
"inherited": false,
|
|
71
|
+
"deprecated": false
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "defaultOpen",
|
|
75
|
+
"type": "boolean",
|
|
76
|
+
"default": "false",
|
|
77
|
+
"description": "Initial open state when uncontrolled. A card that starts open stays open until the pointer or focus has been and gone, or Escape closes it.",
|
|
78
|
+
"required": false,
|
|
79
|
+
"inherited": false,
|
|
80
|
+
"deprecated": false
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "onOpenChange",
|
|
84
|
+
"type": "(open: boolean) => void",
|
|
85
|
+
"default": null,
|
|
86
|
+
"description": "Called with the state asked for: when `openDelay` or `closeDelay` runs out, and right away on Escape. Fires in both controlled and uncontrolled use. An Escape it reports stops at the card when focus is in the card, on its trigger or nowhere, so nothing around it sees that key. With focus anywhere else, the card still closes and the key goes on to whatever has focus.",
|
|
87
|
+
"required": false,
|
|
88
|
+
"inherited": false,
|
|
89
|
+
"deprecated": false
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "openDelay",
|
|
93
|
+
"type": "number",
|
|
94
|
+
"default": "600",
|
|
95
|
+
"description": "How long the pointer or keyboard focus waits on the trigger before the card opens, in milliseconds.",
|
|
96
|
+
"required": false,
|
|
97
|
+
"inherited": false,
|
|
98
|
+
"deprecated": false
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "closeDelay",
|
|
102
|
+
"type": "number",
|
|
103
|
+
"default": "300",
|
|
104
|
+
"description": "How long the card stays after the pointer and focus have left the trigger and the card, in milliseconds. It is the time the pointer has to cross the gap between the trigger and the card: with `0` the card closes as the pointer leaves the trigger, before the pointer can reach it.",
|
|
105
|
+
"required": false,
|
|
106
|
+
"inherited": false,
|
|
107
|
+
"deprecated": false
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "position",
|
|
111
|
+
"type": "'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | 'auto'",
|
|
112
|
+
"default": "'bottom-left'",
|
|
113
|
+
"description": "Where the card opens against the trigger.",
|
|
114
|
+
"required": false,
|
|
115
|
+
"inherited": false,
|
|
116
|
+
"deprecated": false
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "appendToBody",
|
|
120
|
+
"type": "boolean",
|
|
121
|
+
"default": "false",
|
|
122
|
+
"description": "Render the card through `Portal`, into the `portalContainer` a `ConfigProvider` sets, `document.body` by default, fixed to the viewport, so an ancestor's `overflow`, `transform` or stacking context can't clip it. A trigger in running text needs it too, since the card's block content can't sit inside a `<p>`. The card then shows from the commit after hydration, never in server markup, and it sits in the Tab order where its container is. At the end of `<body>`, the default, Tab reaches it only after the rest of the page, and focus leaving the trigger on the way closes it, so keyboard users can open it, read it and close it with Escape, and whatever it links to has to be reachable from the page as well. It is measured against the trigger when it opens and on resize and scroll, so it doesn't follow content that changes size while it is open. Theming variables set on an ancestor of the trigger reach it only when its container is inside that ancestor.",
|
|
123
|
+
"required": false,
|
|
124
|
+
"inherited": false,
|
|
125
|
+
"deprecated": false
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "openOnTap",
|
|
129
|
+
"type": "boolean",
|
|
130
|
+
"default": "false",
|
|
131
|
+
"description": "Open the card with a tap on a touch screen. Touch has no hover, so without this a phone or tablet never opens the card. With it, a tap on the trigger opens the card at once, and it stays until a second tap on the trigger, a tap outside the trigger and the card, or Escape. The tap still does whatever the trigger does, so turn this on only for a trigger with no tap action of its own, such as a `Button` with no `onClick`: on a link, the first tap would follow the link rather than show the card. Mouse and pen work the same either way.",
|
|
132
|
+
"required": false,
|
|
133
|
+
"inherited": false,
|
|
134
|
+
"deprecated": false
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "contentClassName",
|
|
138
|
+
"type": "string",
|
|
139
|
+
"default": null,
|
|
140
|
+
"description": "Additional classes for the card (`.hover-card-content`).",
|
|
141
|
+
"required": false,
|
|
142
|
+
"inherited": false,
|
|
143
|
+
"deprecated": false
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"extraProps": [
|
|
147
|
+
{
|
|
148
|
+
"name": "className",
|
|
149
|
+
"type": "string",
|
|
150
|
+
"default": null,
|
|
151
|
+
"description": "Additional classes for the `span` around the trigger. The card takes `contentClassName`.",
|
|
152
|
+
"required": false,
|
|
153
|
+
"inherited": false,
|
|
154
|
+
"deprecated": false
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"catchAll": "All standard <span> attributes and Bulma helper props",
|
|
158
|
+
"types": []
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
"cssVars": [
|
|
162
|
+
{
|
|
163
|
+
"css": "--bulma-hover-card-z-index",
|
|
164
|
+
"sass": "$hover-card-z-index",
|
|
165
|
+
"default": "$extras-z-index-tooltip",
|
|
166
|
+
"scope": "element"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"css": "--bulma-hover-card-background",
|
|
170
|
+
"sass": "$hover-card-background",
|
|
171
|
+
"default": "var(--bulma-scheme-main)",
|
|
172
|
+
"scope": "element"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"css": "--bulma-hover-card-color",
|
|
176
|
+
"sass": "$hover-card-color",
|
|
177
|
+
"default": "var(--bulma-text)",
|
|
178
|
+
"scope": "element"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"css": "--bulma-hover-card-border-color",
|
|
182
|
+
"sass": "$hover-card-border-color",
|
|
183
|
+
"default": "var(--bulma-border-weak)",
|
|
184
|
+
"scope": "element"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"css": "--bulma-hover-card-radius",
|
|
188
|
+
"sass": "$hover-card-radius",
|
|
189
|
+
"default": "var(--bulma-radius-large)",
|
|
190
|
+
"scope": "element"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"css": "--bulma-hover-card-shadow",
|
|
194
|
+
"sass": "$hover-card-shadow",
|
|
195
|
+
"default": "var(--bulma-shadow)",
|
|
196
|
+
"scope": "element"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"css": "--bulma-hover-card-padding",
|
|
200
|
+
"sass": "$hover-card-padding",
|
|
201
|
+
"default": "1rem",
|
|
202
|
+
"scope": "element"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"css": "--bulma-hover-card-min-width",
|
|
206
|
+
"sass": "$hover-card-min-width",
|
|
207
|
+
"default": "12rem",
|
|
208
|
+
"scope": "element"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"css": "--bulma-hover-card-max-width",
|
|
212
|
+
"sass": "$hover-card-max-width",
|
|
213
|
+
"default": "min(20rem, calc(100vw - 2rem))",
|
|
214
|
+
"scope": "element"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"css": "--bulma-hover-card-offset",
|
|
218
|
+
"sass": "$hover-card-offset",
|
|
219
|
+
"default": "0.25rem",
|
|
220
|
+
"scope": "element"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"css": "--bulma-hover-card-animation-duration",
|
|
224
|
+
"sass": "$hover-card-animation-duration",
|
|
225
|
+
"default": "0.15s",
|
|
226
|
+
"scope": "element"
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"title": "Portal",
|
|
34
|
-
"code": "function example() {\n const [open, setOpen] = useState(false);\n\n return (\n <>\n <Button color=\"success\" onClick={() => setOpen(true)}>\n Show Portaled Modal\n </Button>\n <Modal\n active={open}\n onClose={() => setOpen(false)}\n modalCardTitle=\"
|
|
34
|
+
"code": "function example() {\n const [open, setOpen] = useState(false);\n\n return (\n <>\n <Button color=\"success\" onClick={() => setOpen(true)}>\n Show Portaled Modal\n </Button>\n <Modal\n active={open}\n onClose={() => setOpen(false)}\n modalCardTitle=\"Portaled Modal\"\n portal\n >\n This modal is portaled, so it escapes any ancestor with an `overflow:\n hidden` style.\n </Modal>\n </>\n );\n}"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"title": "Forwarded ref",
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
"name": "portal",
|
|
181
181
|
"type": "boolean | string | HTMLElement",
|
|
182
182
|
"default": "false",
|
|
183
|
-
"description": "Renders the modal into a portal target instead of inline, so it isn't clipped by an ancestor with `overflow: hidden`, `filter` or `transform`. `true` portals to `document.body
|
|
183
|
+
"description": "Renders the modal into a portal target instead of inline, so it isn't clipped by an ancestor with `overflow: hidden`, `filter` or `transform`. `true` portals to the `portalContainer` a `ConfigProvider` sets, `document.body` by default; a string is used as a `document.querySelector` selector; an element is used directly. Renders inline on the server and while hydrating, moving into the portal once the client takes over, so hydration matches.",
|
|
184
184
|
"required": false,
|
|
185
185
|
"inherited": false,
|
|
186
186
|
"deprecated": false
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"accessibility": "`Popover` follows the [WAI-ARIA dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\nfor a non-modal dialog:\n\n- The panel is a `role=\"dialog\"` without `aria-modal`. It is named by its `Popover.Header` through\n `aria-labelledby`, or by `ariaLabel`, which takes precedence. A development build warns when an\n open panel has neither.\n- The trigger gets `aria-haspopup=\"dialog\"`, `aria-expanded`, and `aria-controls` pointing at the\n panel while it is open.\n- Opening moves focus to the first control in the panel, or to the panel itself when it has none.\n Closing hands focus back to the trigger, as long as focus is still in the panel then, so a click\n that put it somewhere else keeps it there.\n- Tab and Shift+Tab wrap inside the open panel, which the pattern asks of non-modal dialogs too.\n Pointer focus, the screen reader's reading cursor and the rest of the page stay free: nothing\n outside is hidden or made inert, and the page still scrolls. `trapFocus={false}` lets Tab leave.\n- <kbd>Escape</kbd> closes the popover while focus is in the panel or on the trigger. A dialog\n nested inside the panel, such as a date picker's calendar or another popover, takes its own\n Escape first.\n- <kbd>Enter</kbd> and <kbd>Space</kbd> toggle a trigger the browser doesn't click on its own.\n- The open animation is turned off under `prefers-reduced-motion: reduce`.",
|
|
42
42
|
"related": [
|
|
43
43
|
"Dropdown",
|
|
44
|
+
"HoverCard",
|
|
44
45
|
"Modal",
|
|
45
46
|
"Portal",
|
|
46
47
|
"Tooltip",
|
|
@@ -116,7 +117,7 @@
|
|
|
116
117
|
"name": "appendToBody",
|
|
117
118
|
"type": "boolean",
|
|
118
119
|
"default": "false",
|
|
119
|
-
"description": "Render the panel
|
|
120
|
+
"description": "Render the panel through `Portal`, into the `portalContainer` a `ConfigProvider` sets, `document.body` by default, fixed to the viewport, so an ancestor's `overflow`, `transform` or stacking context can't clip it. The panel then shows from the commit after hydration, never in server markup. It is measured against the trigger when it opens and on resize and scroll, so it doesn't follow content that changes size while it is open. Theming variables set on an ancestor of the trigger reach it only when its container is inside that ancestor.",
|
|
120
121
|
"required": false,
|
|
121
122
|
"inherited": false,
|
|
122
123
|
"deprecated": false
|
|
@@ -13,22 +13,26 @@
|
|
|
13
13
|
"title": "Into a container",
|
|
14
14
|
"code": "function example() {\n const [target, setTarget] = useState<HTMLElement | null>(null);\n\n return (\n <>\n <Box overflow=\"clipped\">\n <p>The notification is declared inside this box.</p>\n {target && (\n <Portal container={target}>\n <Notification color=\"success\">\n It renders into the element after the box instead.\n </Notification>\n </Portal>\n )}\n </Box>\n <div ref={setTarget} />\n </>\n );\n}"
|
|
15
15
|
},
|
|
16
|
+
{
|
|
17
|
+
"title": "A default container",
|
|
18
|
+
"code": "<ConfigProvider portalContainer=\"#overlays\">\n <App />\n</ConfigProvider>"
|
|
19
|
+
},
|
|
16
20
|
{
|
|
17
21
|
"title": "Rendering in place",
|
|
18
22
|
"code": "<Portal disabled>\n <Notification>Rendered in place, as if there were no portal.</Notification>\n</Portal>"
|
|
19
23
|
}
|
|
20
24
|
],
|
|
21
25
|
"accessibility": "- The DOM decides reading and Tab order, not the React tree. Content portaled to the end of `document.body` comes last in both, so a keyboard user can't reach it from the button that opened it by pressing Tab. Move focus into interactive floating content when it opens, and back to what opened it when it closes.\n- Give floating content a role and an accessible name (`role=\"dialog\"` with `aria-label` or `aria-labelledby`), and give its trigger `aria-expanded` and `aria-controls`. Ids work across the portal, since they are shared by the whole document.\n- Content that only appears after hydration is missing from the server HTML. Keep anything a reader or a crawler needs on first load out of a portal.",
|
|
22
|
-
"related": ["Modal", "Theme", "Toast"],
|
|
26
|
+
"related": ["ConfigProvider", "Modal", "Theme", "Toast"],
|
|
23
27
|
"storybook": "https://bestax.io/storybook/?path=/story/helpers-portal--default",
|
|
24
|
-
"summary": "Renders its children into another part of the page, `document.body` unless `container` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context. React's server renderer can't render a portal, so `Portal` renders nothing on the server and during hydration, and mounts its children in the commit that follows. The first client render then matches the server markup. In an app without server rendering it portals on the first render. React context, such as `ConfigProvider`'s class prefix, reaches the portaled children, and their React events bubble through the React tree. CSS inheritance follows the DOM instead: a scoped (non-`isRoot`) `Theme` sets its variables on a wrapper element the portaled content has left, so that content sees the page-level theme
|
|
28
|
+
"summary": "Renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context. React's server renderer can't render a portal, so `Portal` renders nothing on the server and during hydration, and mounts its children in the commit that follows. The first client render then matches the server markup. In an app without server rendering it portals on the first render. React context, such as `ConfigProvider`'s class prefix, reaches the portaled children, and their React events bubble through the React tree. CSS inheritance follows the DOM instead: a scoped (non-`isRoot`) `Theme` sets its variables on a wrapper element the portaled content has left, so that content sees the page-level theme, and styles scoped to a shadow root don't reach `document.body` at all. To keep them, portal into an element inside the themed area or the shadow root: pass it as `container`, or as the `portalContainer` of a `ConfigProvider` around that area. The second also reaches what the library's components portal, such as Popover's `appendToBody` panel or a `Modal` with `portal`. Tab order and focus follow the DOM too. Content portaled from inside a focus trap is no longer inside the trapped element, so the trap neither reaches it nor holds focus there: render nested overlays inside the trapped element, or give them a trap of their own. To trap portaled content, put `useFocusTrap`'s ref on the element inside the `Portal`.",
|
|
25
29
|
"import": "import { Portal } from '@allxsmith/bestax-bulma';",
|
|
26
30
|
"sourceFile": "bulma-ui/src/helpers/portal.tsx",
|
|
27
31
|
"rootClass": null,
|
|
28
32
|
"parts": [
|
|
29
33
|
{
|
|
30
34
|
"path": "Portal",
|
|
31
|
-
"summary": "Renders its children into another part of the page, `document.body` unless `container` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context. React's server renderer can't render a portal, so `Portal` renders nothing on the server and during hydration, and mounts its children in the commit that follows. The first client render then matches the server markup. In an app without server rendering it portals on the first render. React context, such as `ConfigProvider`'s class prefix, reaches the portaled children, and their React events bubble through the React tree. CSS inheritance follows the DOM instead: a scoped (non-`isRoot`) `Theme` sets its variables on a wrapper element the portaled content has left, so that content sees the page-level theme
|
|
35
|
+
"summary": "Renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context. React's server renderer can't render a portal, so `Portal` renders nothing on the server and during hydration, and mounts its children in the commit that follows. The first client render then matches the server markup. In an app without server rendering it portals on the first render. React context, such as `ConfigProvider`'s class prefix, reaches the portaled children, and their React events bubble through the React tree. CSS inheritance follows the DOM instead: a scoped (non-`isRoot`) `Theme` sets its variables on a wrapper element the portaled content has left, so that content sees the page-level theme, and styles scoped to a shadow root don't reach `document.body` at all. To keep them, portal into an element inside the themed area or the shadow root: pass it as `container`, or as the `portalContainer` of a `ConfigProvider` around that area. The second also reaches what the library's components portal, such as Popover's `appendToBody` panel or a `Modal` with `portal`. Tab order and focus follow the DOM too. Content portaled from inside a focus trap is no longer inside the trapped element, so the trap neither reaches it nor holds focus there: render nested overlays inside the trapped element, or give them a trap of their own. To trap portaled content, put `useFocusTrap`'s ref on the element inside the `Portal`.",
|
|
32
36
|
"component": null,
|
|
33
37
|
"props": [
|
|
34
38
|
{
|
|
@@ -44,7 +48,7 @@
|
|
|
44
48
|
"name": "container",
|
|
45
49
|
"type": "string | HTMLElement",
|
|
46
50
|
"default": null,
|
|
47
|
-
"description": "Where the content goes: an element, or a `document.querySelector` selector. Omitted
|
|
51
|
+
"description": "Where the content goes: an element, or a `document.querySelector` selector. Omitted or empty, it goes to the `portalContainer` a `ConfigProvider` sets, and to `document.body` when none is set. A selector that matches nothing means `document.body` too. A selector is looked up again on each render, so the content follows the target when it changes. Moving to another target remounts the children and resets their state, which includes a selector that only starts matching after the content has shown in `document.body`. Render the target first, or pass the element once you have it.",
|
|
48
52
|
"required": false,
|
|
49
53
|
"inherited": false,
|
|
50
54
|
"deprecated": false
|
|
@@ -65,5 +69,5 @@
|
|
|
65
69
|
}
|
|
66
70
|
],
|
|
67
71
|
"cssVars": [],
|
|
68
|
-
"doc": "# Portal\n\n## Overview\n\n`Portal` renders its children into another part of the page, `document.body` unless `container` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context.\n\nIt is the building block for your own overlays: a command palette, a floating panel. `Modal`, `
|
|
72
|
+
"doc": "# Portal\n\n## Overview\n\n`Portal` renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise, so floating content escapes an ancestor's `overflow`, `transform` or stacking context.\n\nIt is the building block for your own overlays: a command palette, a floating panel. The library's own overlays, such as `Modal`, `Popover` and `HoverCard`, already portal through their own props, so reach for `Portal` when you are building something they don't cover.\n\nReact's server renderer can't render a portal, so `Portal` renders nothing on the server and during hydration, and mounts its children in the commit that follows. The first client render matches the server markup, and there is no hydration warning to chase. In an app without server rendering it portals on the first render.\n\n---\n\n## Import\n\n```tsx\nimport { Portal } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Usage\n\n### Into `document.body`\n\nWith no `container`, and no [default container](https://bestax.io/docs/api/helpers/portal#a-default-container) set, the children go to the end of `document.body`, whatever clips or transforms the component that renders them.\n\n```tsx\nfunction Banner() {\n return (\n <Box overflow=\"clipped\">\n <p>This box clips its overflow.</p>\n <Portal>\n <Notification color=\"info\">\n Rendered at the end of document.body, outside the box.\n </Notification>\n </Portal>\n </Box>\n );\n}\n```\n\n### Into a container\n\n`container` takes an element or a `document.querySelector` selector. A selector suits an overlay root in your page shell (`<div id=\"overlays\" />`); an element suits a target your own component renders. Hold that element in state from a ref callback, so the portal renders once the target exists.\n\n```tsx live\nfunction example() {\n const [target, setTarget] = useState<HTMLElement | null>(null);\n\n return (\n <>\n <Box overflow=\"clipped\">\n <p>The notification is declared inside this box.</p>\n {target && (\n <Portal container={target}>\n <Notification color=\"success\">\n It renders into the element after the box instead.\n </Notification>\n </Portal>\n )}\n </Box>\n <div ref={setTarget} />\n </>\n );\n}\n```\n\nAn empty selector counts as no `container`. One that matches nothing falls back to `document.body` rather than throwing.\n\nA selector is looked up again on each render, so the content follows the target when the target changes. Moving to another target remounts the children, which resets their state. That includes a selector that only starts matching after the content has already shown in `document.body`, so render the target first (an overlay root in the page shell is), or pass the element once you have it, as above.\n\n### A default container\n\nA `ConfigProvider` with a `portalContainer` sets where every `Portal` inside it goes when it has no `container` of its own. The library's components that portal follow it too, such as `Popover`'s `appendToBody` panel or a `Modal` with `portal`. It takes the same element or selector as `container`, and follows the same rules, including the remount when the target turns up after the content has rendered. What those components portal is `position: fixed`, so the element has to sit where no ancestor has a `transform`, `filter`, `backdrop-filter`, `perspective`, `will-change: transform`, or a `contain` that includes `paint` or `layout`, any of which would place it against that ancestor instead of the viewport. The [ConfigProvider page](https://bestax.io/docs/api/helpers/config#where-portaled-content-goes) has the rest, and an example.\n\n```tsx\n<ConfigProvider portalContainer=\"#overlays\">\n <App />\n</ConfigProvider>\n```\n\n### Rendering in place\n\n`disabled` renders the children where the `Portal` is, on the server as well as the client. A component that portals only sometimes (behind an `appendToBody` option, say) can keep one code path.\n\n```tsx live\n<Portal disabled>\n <Notification>Rendered in place, as if there were no portal.</Notification>\n</Portal>\n```\n\n### Floating content and focus\n\nPositioning floating content next to its trigger is up to you; `Portal` only decides where it sits in the DOM.\n\nFocus follows the DOM too. Content portaled to the end of `document.body` is last in the Tab order, so interactive floating content should take focus when it opens and hand it back to its trigger when it closes. A focus trap doesn't cover what a `Portal` inside it renders, because that content is no longer inside the trapped element: render a nested overlay inside the trapped element, or trap it separately. To trap portaled content, put the trap on the element inside the `Portal`, as the [useFocusTrap portal example](https://bestax.io/docs/api/helpers/usefocustrap#with-portals) does.\n\n### Context and styles\n\nReact context reaches portaled children, so a `ConfigProvider` class prefix still applies, and their React events bubble up the React tree to the components around the `Portal`. CSS inheritance follows the DOM instead. A scoped `Theme` (one without `isRoot`) sets its CSS variables on a wrapper element that portaled content has left, so that content sees the page-level theme, and styles scoped to a shadow root don't reach `document.body` at all. To keep them, portal into an element inside the themed area or the shadow root: pass it as `container`, or make it the [default container](https://bestax.io/docs/api/helpers/portal#a-default-container) of a `ConfigProvider` around that area, which also reaches the content components such as `Popover` portal.\n\n---\n\n## Accessibility\n\n- The DOM decides reading and Tab order, not the React tree. Content portaled to the end of `document.body` comes last in both, so a keyboard user can't reach it from the button that opened it by pressing Tab. Move focus into interactive floating content when it opens, and back to what opened it when it closes.\n- Give floating content a role and an accessible name (`role=\"dialog\"` with `aria-label` or `aria-labelledby`), and give its trigger `aria-expanded` and `aria-controls`. Ids work across the portal, since they are shared by the whole document.\n- Content that only appears after hydration is missing from the server HTML. Keep anything a reader or a crawler needs on first load out of a portal.\n\n---\n\n## Related Components\n\n- [`Modal`](https://bestax.io/docs/api/components/modal): Has its own `portal` prop, taking the same `true`, selector or element.\n- [`Toast`](https://bestax.io/docs/api/components/toast): Portals unless `inline` is set, into its own `container` when it has one.\n- [`Theme`](https://bestax.io/docs/api/helpers/theme): Scoped themes stay behind when content is portaled; see above.\n- [`ConfigProvider`](https://bestax.io/docs/api/helpers/config): Its `portalContainer` is where a `Portal` with no `container` goes.\n\n---\n\n## Additional Resources\n\n- [Storybook: Portal Stories](https://bestax.io/storybook/?path=/story/helpers-portal--default)\n- [React: `createPortal`](https://react.dev/reference/react-dom/createPortal)\n\n---\n\n## Props\n\n| Prop | Type | Default | Description |\n| ----------- | ------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `children` | `ReactNode` | — | Content to render into the container. |\n| `container` | `string` \\| `HTMLElement` | a `ConfigProvider`'s `portalContainer`, else `document.body` | An element, or a `document.querySelector` selector looked up on each render. Omitted or empty, it is the `portalContainer` a `ConfigProvider` sets, or `document.body` when none is set. A selector that matches nothing means `document.body`. Moving to another target remounts the children. |\n| `disabled` | `boolean` | `false` | Render the children in place instead, on the server as well as the client. |"
|
|
69
73
|
}
|
|
@@ -546,7 +546,7 @@
|
|
|
546
546
|
"name": "appendToBody",
|
|
547
547
|
"type": "boolean",
|
|
548
548
|
"default": "false",
|
|
549
|
-
"description": "Render the popover into `document.body`
|
|
549
|
+
"description": "Render the popover through a portal, into the `portalContainer` a `ConfigProvider` sets, `document.body` by default. On a narrow screen an in-place popover spans the bottom of the viewport, while a portaled one stays on its `position` corner.",
|
|
550
550
|
"required": false,
|
|
551
551
|
"inherited": true,
|
|
552
552
|
"deprecated": false
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
}
|
|
48
48
|
],
|
|
49
49
|
"accessibility": "- Tooltip content has `role=\"tooltip\"` for screen reader announcement\n- Has `aria-hidden` that toggles based on visibility\n- Tooltip shows on both hover and focus for keyboard users\n- The `dashed` style provides a visual cue that more information is available",
|
|
50
|
-
"related": [],
|
|
50
|
+
"related": ["Button", "HoverCard", "Icon", "Popover"],
|
|
51
51
|
"storybook": null,
|
|
52
52
|
"summary": "The `Tooltip` component displays helpful information when users hover over or focus on an element.",
|
|
53
53
|
"import": "import { Tooltip } from '@allxsmith/bestax-bulma';",
|
|
@@ -67,6 +67,13 @@ A panel opened from a button (a filter form, share options, an inline edit) is `
|
|
|
67
67
|
it rather than building one. It anchors the panel, moves focus in and back, and closes on
|
|
68
68
|
Escape and outside presses.
|
|
69
69
|
|
|
70
|
+
A preview that shows up on hover (a profile card on a username, a summary on a link) is
|
|
71
|
+
`HoverCard`: use it rather than wiring pointer events to a `Popover`. It opens on hover and
|
|
72
|
+
keyboard focus after a delay, lets the pointer move onto the card, closes on Escape, and ignores
|
|
73
|
+
touch unless `openOnTap` is on, which suits only a trigger with no tap action of its own (a
|
|
74
|
+
`Button` with no `onClick`, never a link). Its `trigger` has to be a link or a button, and a trigger in running text takes
|
|
75
|
+
`appendToBody`. A form never goes in one; that's `Popover`.
|
|
76
|
+
|
|
70
77
|
Building something else that floats (a command palette)? Render it through the library's
|
|
71
78
|
`Portal` rather than `createPortal`: it renders nothing on the server and during hydration, so
|
|
72
79
|
the server and client markup match. `references/api.md` has the details.
|
|
@@ -88,7 +88,8 @@ union (a compile error beats a silent no-op).
|
|
|
88
88
|
|
|
89
89
|
## `ConfigProvider` / `Theme` — `helpers/Config.tsx`, `helpers/Theme.tsx`
|
|
90
90
|
|
|
91
|
-
`ConfigProvider` provides the runtime `classPrefix` (and `iconLibrary`
|
|
91
|
+
`ConfigProvider` provides the runtime `classPrefix` (and `iconLibrary` and `portalContainer`)
|
|
92
|
+
consumed via `useConfig`;
|
|
92
93
|
`classPrefix` feeds `usePrefixedClassNames` (opt-in class prefixing to avoid collisions). `Theme`
|
|
93
94
|
overrides `--bulma-*` custom properties at runtime —
|
|
94
95
|
which is exactly why component SCSS must register its vars via `cv.register-vars` rather than
|
|
@@ -114,17 +115,20 @@ hard-coding values.
|
|
|
114
115
|
## Floating content: `Portal`
|
|
115
116
|
|
|
116
117
|
A panel anchored to the button that opens it is `Popover`, which already portals
|
|
117
|
-
(`appendToBody`), traps focus and dismisses itself
|
|
118
|
-
cover.
|
|
118
|
+
(`appendToBody`), traps focus and dismisses itself, and a preview that opens on hover is
|
|
119
|
+
`HoverCard`, which portals the same way. `Portal` is for the overlays they don't cover.
|
|
119
120
|
|
|
120
|
-
`<Portal container?>` (`helpers/portal.tsx`) renders its children into `
|
|
121
|
-
|
|
121
|
+
`<Portal container?>` (`helpers/portal.tsx`) renders its children into `container` (an element or
|
|
122
|
+
a selector), or without one into the `portalContainer` a `ConfigProvider` sets, and
|
|
123
|
+
`document.body` when none is set, so floating content escapes an ancestor's `overflow`,
|
|
122
124
|
`transform` or stacking context. It renders nothing on the server and during hydration, so the
|
|
123
125
|
first client render matches; `disabled` renders in place instead. Use it rather than calling
|
|
124
126
|
`createPortal` yourself, which React's server renderer can't render.
|
|
125
127
|
|
|
126
|
-
Focus follows the DOM, not the React tree: portaled content
|
|
127
|
-
|
|
128
|
+
Focus follows the DOM, not the React tree: portaled content sits in the Tab order where its
|
|
129
|
+
container is, which for the default `document.body` is last, so an overlay you build that takes
|
|
130
|
+
focus moves it in when it opens and back to its trigger when it closes. A `HoverCard` moves no
|
|
131
|
+
focus when it opens. To trap portaled content,
|
|
128
132
|
put the trap's ref on the element inside the `Portal`, as below with
|
|
129
133
|
`useFocusTrap(panelRef, { active: open, restoreFocus: buttonRef })`. A focus trap doesn't cover
|
|
130
134
|
what a `Portal` inside it renders, so render nested overlays inside the trapped element.
|
|
@@ -141,6 +145,16 @@ what a `Portal` inside it renders, so render nested overlays inside the trapped
|
|
|
141
145
|
}
|
|
142
146
|
```
|
|
143
147
|
|
|
148
|
+
Content in `document.body` loses a scoped `Theme`'s variables and a shadow root's styles. In a web
|
|
149
|
+
component or a themed section, wrap the area in a `ConfigProvider` whose `portalContainer` is an
|
|
150
|
+
element inside it, which also covers what the library's components portal, such as `Popover`'s
|
|
151
|
+
`appendToBody` panel or a `Modal` with `portal`. In a shadow root, pass the
|
|
152
|
+
element itself, held in state from a ref callback, since a selector can't reach inside one. What
|
|
153
|
+
moves there is `position: fixed`, so the element must not sit under an ancestor with a
|
|
154
|
+
`transform`, `filter`, `backdrop-filter`, `perspective`, `will-change: transform`, or a `contain`
|
|
155
|
+
that includes `paint` or `layout`: that ancestor becomes the containing block, and overlays and
|
|
156
|
+
anchored panels land against it instead of the viewport.
|
|
157
|
+
|
|
144
158
|
## Holding focus: `useFocusTrap`
|
|
145
159
|
|
|
146
160
|
`useFocusTrap(ref, { active, initialFocusRef, restoreFocus })` (`helpers/useFocusTrap.ts`) moves
|
|
@@ -37,7 +37,7 @@ instead of hand-writing markup.
|
|
|
37
37
|
- Raw `*Base` form exports (`InputBase`, `SelectBase`, `TextAreaBase`, …) are
|
|
38
38
|
escape-hatch variants of the convenience wrappers above them; see the Form docs.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
95 documented components. Generated from the API docs — every exported
|
|
41
41
|
component is guaranteed to appear (the generator fails if one lacks an API page).
|
|
42
42
|
|
|
43
43
|
## Elements
|
|
@@ -86,6 +86,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
86
86
|
- [Collapses](https://bestax.io/docs/api/components/collapses) — The `Collapses` component groups `Collapse` items into an accordion that keeps one item open at a time, or any number with `multiple`.
|
|
87
87
|
- [Dialog](https://bestax.io/docs/api/components/dialog) — The `Dialog` component provides ready-made confirm and alert dialogs, so a destructive action stays one `await dialog.confirm()` call away.
|
|
88
88
|
- [Dropdown](https://bestax.io/docs/api/components/dropdown) — The `Dropdown` component provides Bulma's versatile dropdown menu for your Bulma React UI.
|
|
89
|
+
- [HoverCard](https://bestax.io/docs/api/components/hovercard) — The `HoverCard` component shows a card previewing what's behind a link or a button while the pointer rests on it or keyboard focus is on it.
|
|
89
90
|
- [Loading](https://bestax.io/docs/api/components/loading) — The `Loading` component provides a loading overlay with a spinner animation.
|
|
90
91
|
- [Menu](https://bestax.io/docs/api/components/menu) — The `Menu` component provides Bulma's vertical navigation menu: a simple, accessible sidebar or section menu for your Bulma React UI.
|
|
91
92
|
- [Message](https://bestax.io/docs/api/components/message) — The `Message` component provides Bulma's flexible notice/message box for your Bulma React UI.
|
|
@@ -147,7 +148,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
147
148
|
|
|
148
149
|
- [ClientOnly](https://bestax.io/docs/api/helpers/clientonly) — `ClientOnly` renders its children only in the browser, after hydration, and a `fallback` until then…
|
|
149
150
|
- [ConfigProvider](https://bestax.io/docs/api/helpers/config) — The `ConfigProvider` component provides a React context for configuring global settings across all Bulma UI components.
|
|
150
|
-
- [Portal](https://bestax.io/docs/api/helpers/portal) — `Portal` renders its children into another part of the page, `document.body` unless `container` says otherwise…
|
|
151
|
+
- [Portal](https://bestax.io/docs/api/helpers/portal) — `Portal` renders its children into another part of the page, `document.body` unless `container` or a `ConfigProvider` says otherwise…
|
|
151
152
|
- [Theme](https://bestax.io/docs/api/helpers/theme) — The `Theme` component provides a powerful way to customize Bulma's appearance using CSS custom properties (CSS variables).
|
|
152
153
|
- [Valid value constants](https://bestax.io/docs/api/helpers/valid-values) — The helper tuples enumerate the core accepted values for the shared Bulma helper props — public API for building prop types and validation.
|
|
153
154
|
- [classNames](https://bestax.io/docs/api/helpers/classnames) — `classNames` is a utility function for conditionally joining class names together.
|
|
@@ -168,7 +168,10 @@ Rules that keep components consistent:
|
|
|
168
168
|
place in the DOM uses `Portal` (`helpers/portal.tsx`) rather than its own `createPortal` and
|
|
169
169
|
`typeof document` check. `Portal` renders nothing on the server or during hydration, so the
|
|
170
170
|
first client render matches the server markup. Test that with `renderToString` plus
|
|
171
|
-
`hydrateRoot` and a `console.error` spy, as `portal.test.tsx` does.
|
|
171
|
+
`hydrateRoot` and a `console.error` spy, as `portal.test.tsx` does. A component that has to
|
|
172
|
+
call `createPortal` itself, as `Modal` does to render in place until hydration, resolves its
|
|
173
|
+
target with `usePortalContainer` from the same module, so the `portalContainer` a
|
|
174
|
+
`ConfigProvider` sets reaches it too. Add it to `src/__tests__/portal-container.test.tsx`.
|
|
172
175
|
|
|
173
176
|
- **Focus management builds on `useFocusTrap`.** A new component that holds focus while it is
|
|
174
177
|
open uses `useFocusTrap` (`helpers/useFocusTrap.ts`) rather than its own Tab handler, so it
|
|
@@ -63,6 +63,12 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
|
|
|
63
63
|
element, not a node: Popover clones it to add the click and ARIA, so never wrap it in a
|
|
64
64
|
clickable of your own; build the panel from `Popover.Header`/`Body`/`Footer`/`Close`, and a
|
|
65
65
|
panel without a `Popover.Header` needs `ariaLabel`),
|
|
66
|
+
`HoverCard trigger={<Link href="…">…</Link>}` for a preview on hover or keyboard focus
|
|
67
|
+
(`trigger` is one link or button, cloned the same way; the card's content goes straight in as
|
|
68
|
+
children with no sub-components; a trigger inside a `Paragraph` needs `appendToBody`; hover
|
|
69
|
+
and focus only unless `openOnTap` is set, which suits only a trigger with no tap action of its
|
|
70
|
+
own, such as a `Button` with no `onClick`; on touch a link trigger just follows the link, so
|
|
71
|
+
what the card shows must be on that page too, and a form is a `Popover`),
|
|
66
72
|
`Steps value={i}/onStepClick items={[{label, icon?}]}` (child form is `Steps.Step`, not
|
|
67
73
|
`Steps.Item`). `Reveal cascade` staggers only its **direct children** — to stagger a grid,
|
|
68
74
|
put `<Reveal delay={i * 80}>` inside each `Cell`, not around the container.
|
|
@@ -73,8 +73,8 @@ Every extras partial that exists (from the library's `src/scss/**/_index.scss`
|
|
|
73
73
|
is **more complete** than the docs page's Option C example, which omits several):
|
|
74
74
|
|
|
75
75
|
**Components** (`@allxsmith/bestax-bulma/scss/components/<name>`):
|
|
76
|
-
`loading`, `collapse`, `tooltip`, `popover`, `steps`, `sidebar`, `toast`, `dialog`,
|
|
77
|
-
`tabs`, `reveal`, `avatar`, `avatars`, `badge`
|
|
76
|
+
`loading`, `collapse`, `tooltip`, `popover`, `hovercard`, `steps`, `sidebar`, `toast`, `dialog`,
|
|
77
|
+
`carousel`, `tabs`, `reveal`, `avatar`, `avatars`, `badge`
|
|
78
78
|
|
|
79
79
|
**Form** (`@allxsmith/bestax-bulma/scss/form/<name>`):
|
|
80
80
|
`checkbox`, `radio`, `switch`, `slider`, `numberinput`, `rate`, `autocomplete`, `taginput`,
|
|
@@ -170,7 +170,8 @@ lone class or the `style` prop cannot outrank:
|
|
|
170
170
|
styles with a selector that matches or exceeds the compound:
|
|
171
171
|
`.button.link-button { --bulma-link-button-ghost-color: … }`.
|
|
172
172
|
- **Constituent elements** — Tooltip's variables (all but `--bulma-tooltip-dashed-color`)
|
|
173
|
-
are declared on `.tooltip-content`, Popover's on its panel, `.popover-content`,
|
|
173
|
+
are declared on `.tooltip-content`, Popover's on its panel, `.popover-content`, HoverCard's
|
|
174
|
+
on its card, `.hover-card-content`, and Sidebar's
|
|
174
175
|
`--bulma-sidebar-overlay-background` on `.sidebar-background`. Values set via `className` or
|
|
175
176
|
`style` land on the component root and are only inherited by the constituent, so they lose to
|
|
176
177
|
its own declaration — target the declaring element in your CSS:
|
|
@@ -297,6 +298,16 @@ The field around the two inputs. Its calendar is the `DateInput` one, with the v
|
|
|
297
298
|
`--bulma-dialog-radius`, `--bulma-dialog-shadow`, `--bulma-dialog-title-color`,
|
|
298
299
|
`--bulma-dialog-title-size`, `--bulma-dialog-title-weight`, `--bulma-dialog-width`
|
|
299
300
|
|
|
301
|
+
### HoverCard
|
|
302
|
+
|
|
303
|
+
All are declared on the card, `.hover-card-content`, so a card rendered with `appendToBody` keeps
|
|
304
|
+
them. See the constituent-element exception above.
|
|
305
|
+
|
|
306
|
+
`--bulma-hover-card-animation-duration`, `--bulma-hover-card-background`,
|
|
307
|
+
`--bulma-hover-card-border-color`, `--bulma-hover-card-color`, `--bulma-hover-card-max-width`,
|
|
308
|
+
`--bulma-hover-card-min-width`, `--bulma-hover-card-offset`, `--bulma-hover-card-padding`,
|
|
309
|
+
`--bulma-hover-card-radius`, `--bulma-hover-card-shadow`, `--bulma-hover-card-z-index`
|
|
310
|
+
|
|
300
311
|
### LinkButton
|
|
301
312
|
|
|
302
313
|
Declared on the compound `.button.link-button` — see the compound-selector exception above.
|
package/data/skills.json
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
"promptName": "custom-component",
|
|
7
7
|
"dir": "bestax-custom-component",
|
|
8
8
|
"references": [
|
|
9
|
-
{ "id": "api", "file": "references/api.md", "bytes":
|
|
9
|
+
{ "id": "api", "file": "references/api.md", "bytes": 11973 },
|
|
10
10
|
{
|
|
11
11
|
"id": "component-catalog",
|
|
12
12
|
"file": "references/component-catalog.md",
|
|
13
|
-
"bytes":
|
|
13
|
+
"bytes": 18983
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"id": "library-contributor",
|
|
17
17
|
"file": "references/library-contributor.md",
|
|
18
|
-
"bytes":
|
|
18
|
+
"bytes": 21340
|
|
19
19
|
},
|
|
20
20
|
{ "id": "patterns", "file": "references/patterns.md", "bytes": 4601 }
|
|
21
21
|
],
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
{
|
|
163
163
|
"id": "modular-build",
|
|
164
164
|
"file": "references/modular-build.md",
|
|
165
|
-
"bytes":
|
|
165
|
+
"bytes": 10126
|
|
166
166
|
}
|
|
167
167
|
],
|
|
168
168
|
"examples": []
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
{
|
|
177
177
|
"id": "css-variables",
|
|
178
178
|
"file": "references/css-variables.md",
|
|
179
|
-
"bytes":
|
|
179
|
+
"bytes": 32153
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
"id": "themeable-components",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bestax-mcp",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.6",
|
|
4
4
|
"mcpName": "io.github.allxsmith/bestax-mcp",
|
|
5
5
|
"description": "MCP server for @allxsmith/bestax-bulma — component props, examples, CSS variables and agent skills, served offline from a version-pinned index",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
44
44
|
"zod": "^4.4.3",
|
|
45
|
-
"@allxsmith/bestax-bulma": "^5.
|
|
45
|
+
"@allxsmith/bestax-bulma": "^5.29.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@jest/globals": "^30.0.0",
|