@malilion/block-ui-react 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/BlockMenu/BlockMenu.d.ts.map +1 -1
- package/dist/components/actions/BlockMenu/BlockMenu.js +17 -117
- package/dist/components/actions/BlockMenu/BlockMenu.js.map +1 -1
- package/dist/components/actions/BlockMenu/BlockMenu.utils.d.ts +2 -0
- package/dist/components/actions/BlockMenu/BlockMenu.utils.d.ts.map +1 -1
- package/dist/components/actions/BlockMenu/BlockMenu.utils.js +5 -1
- package/dist/components/actions/BlockMenu/BlockMenu.utils.js.map +1 -1
- package/dist/components/actions/BlockMenu/MenuList.d.ts +28 -0
- package/dist/components/actions/BlockMenu/MenuList.d.ts.map +1 -0
- package/dist/components/actions/BlockMenu/MenuList.js +134 -0
- package/dist/components/actions/BlockMenu/MenuList.js.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.d.ts +8 -0
- package/dist/components/actions/ContextMenu/ContextMenu.d.ts.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.js +100 -0
- package/dist/components/actions/ContextMenu/ContextMenu.js.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.module.js +7 -0
- package/dist/components/actions/ContextMenu/ContextMenu.module.js.map +1 -0
- package/dist/components/actions/ContextMenu/ContextMenu.types.d.ts +15 -0
- package/dist/components/actions/ContextMenu/ContextMenu.types.d.ts.map +1 -0
- package/dist/components/actions/ContextMenu/index.d.ts +3 -0
- package/dist/components/actions/ContextMenu/index.d.ts.map +1 -0
- package/dist/components/actions/index.d.ts +1 -0
- package/dist/components/actions/index.d.ts.map +1 -1
- package/dist/components/crafting/RecipeBook/RecipeBook.js +2 -2
- package/dist/components/crafting/RecipeBook/RecipeBook.js.map +1 -1
- package/dist/components/display/Kbd/Kbd.d.ts +4 -0
- package/dist/components/display/Kbd/Kbd.d.ts.map +1 -0
- package/dist/components/display/Kbd/Kbd.js +33 -0
- package/dist/components/display/Kbd/Kbd.js.map +1 -0
- package/dist/components/display/Kbd/Kbd.module.js +13 -0
- package/dist/components/display/Kbd/Kbd.module.js.map +1 -0
- package/dist/components/display/Kbd/Kbd.types.d.ts +9 -0
- package/dist/components/display/Kbd/Kbd.types.d.ts.map +1 -0
- package/dist/components/display/Kbd/index.d.ts +3 -0
- package/dist/components/display/Kbd/index.d.ts.map +1 -0
- package/dist/components/display/Popover/Popover.d.ts +8 -0
- package/dist/components/display/Popover/Popover.d.ts.map +1 -0
- package/dist/components/display/Popover/Popover.js +101 -0
- package/dist/components/display/Popover/Popover.js.map +1 -0
- package/dist/components/display/Popover/Popover.module.js +15 -0
- package/dist/components/display/Popover/Popover.module.js.map +1 -0
- package/dist/components/display/Popover/Popover.types.d.ts +26 -0
- package/dist/components/display/Popover/Popover.types.d.ts.map +1 -0
- package/dist/components/display/Popover/index.d.ts +3 -0
- package/dist/components/display/Popover/index.d.ts.map +1 -0
- package/dist/components/display/SkillTree/SkillTree.js +2 -2
- package/dist/components/display/SkillTree/SkillTree.js.map +1 -1
- package/dist/components/display/index.d.ts +2 -0
- package/dist/components/display/index.d.ts.map +1 -1
- package/dist/components/feedback/ConfirmDialog/ConfirmDialog.js +2 -2
- package/dist/components/feedback/ConfirmDialog/ConfirmDialog.js.map +1 -1
- package/dist/components/inventory/InventorySlot/InventorySlot.js +2 -2
- package/dist/components/inventory/InventorySlot/InventorySlot.js.map +1 -1
- package/dist/components/layout/BlockContainer/BlockContainer.d.ts +4 -0
- package/dist/components/layout/BlockContainer/BlockContainer.d.ts.map +1 -0
- package/dist/components/layout/BlockContainer/BlockContainer.js +18 -0
- package/dist/components/layout/BlockContainer/BlockContainer.js.map +1 -0
- package/dist/components/layout/BlockContainer/BlockContainer.module.js +7 -0
- package/dist/components/layout/BlockContainer/BlockContainer.module.js.map +1 -0
- package/dist/components/layout/BlockContainer/BlockContainer.types.d.ts +11 -0
- package/dist/components/layout/BlockContainer/BlockContainer.types.d.ts.map +1 -0
- package/dist/components/layout/BlockContainer/index.d.ts +3 -0
- package/dist/components/layout/BlockContainer/index.d.ts.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.d.ts +4 -0
- package/dist/components/layout/BlockGrid/BlockGrid.d.ts.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.js +25 -0
- package/dist/components/layout/BlockGrid/BlockGrid.js.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.module.js +7 -0
- package/dist/components/layout/BlockGrid/BlockGrid.module.js.map +1 -0
- package/dist/components/layout/BlockGrid/BlockGrid.types.d.ts +16 -0
- package/dist/components/layout/BlockGrid/BlockGrid.types.d.ts.map +1 -0
- package/dist/components/layout/BlockGrid/index.d.ts +3 -0
- package/dist/components/layout/BlockGrid/index.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/navigation/BlockSidebar/BlockSidebar.js +3 -3
- package/dist/components/navigation/BlockSidebar/BlockSidebar.js.map +1 -1
- package/dist/components/navigation/BlockStepper/BlockStepper.d.ts +8 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.d.ts.map +1 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.js +66 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.js.map +1 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.module.js +23 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.module.js.map +1 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.types.d.ts +19 -0
- package/dist/components/navigation/BlockStepper/BlockStepper.types.d.ts.map +1 -0
- package/dist/components/navigation/BlockStepper/index.d.ts +3 -0
- package/dist/components/navigation/BlockStepper/index.d.ts.map +1 -0
- package/dist/components/navigation/Breadcrumb/Breadcrumb.js +2 -2
- package/dist/components/navigation/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/navigation/HotbarNavigation/HotbarNavigation.js +3 -3
- package/dist/components/navigation/HotbarNavigation/HotbarNavigation.js.map +1 -1
- package/dist/components/navigation/index.d.ts +1 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/hooks/useAnchoredPosition.d.ts +3 -1
- package/dist/hooks/useAnchoredPosition.d.ts.map +1 -1
- package/dist/hooks/useAnchoredPosition.js.map +1 -1
- package/dist/index.js +7 -1
- package/dist/styles.css +281 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
|
|
@@ -8,7 +8,7 @@ import RecipeBook_module_default from "./RecipeBook.module.js";
|
|
|
8
8
|
import { deriveCategories, filterRecipes } from "./RecipeBook.utils.js";
|
|
9
9
|
import { forwardRef, useId, useState } from "react";
|
|
10
10
|
import { ArrowIcon, SearchIcon } from "@malilion/block-ui-icons";
|
|
11
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
//#region src/components/crafting/RecipeBook/RecipeBook.tsx
|
|
13
13
|
/**
|
|
14
14
|
* Recipe book: search, category filters and a "craftable only" switch over a
|
|
@@ -104,7 +104,7 @@ var RecipeBook = forwardRef(function RecipeBook({ recipes, categories, value, de
|
|
|
104
104
|
id: titleId,
|
|
105
105
|
className: RecipeBook_module_default.title,
|
|
106
106
|
children: selected ? selected.name : "Select a recipe"
|
|
107
|
-
}), selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
107
|
+
}), selected ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
108
108
|
/* @__PURE__ */ jsxs("div", {
|
|
109
109
|
className: RecipeBook_module_default.preview,
|
|
110
110
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecipeBook.js","names":["styles","_Fragment"],"sources":["../../../../src/components/crafting/RecipeBook/RecipeBook.tsx"],"sourcesContent":["import { ArrowIcon, SearchIcon } from \"@malilion/block-ui-icons\";\nimport { forwardRef, useId, useState } from \"react\";\nimport { useControllableState } from \"../../../hooks/useControllableState\";\nimport { cx } from \"../../../utils/cx\";\nimport { BlockButton } from \"../../actions/BlockButton/BlockButton\";\nimport { BlockInput } from \"../../forms/BlockInput/BlockInput\";\nimport { BlockToggle } from \"../../forms/BlockToggle/BlockToggle\";\nimport { InventorySlot } from \"../../inventory/InventorySlot/InventorySlot\";\nimport styles from \"./RecipeBook.module.css\";\nimport type { RecipeBookProps } from \"./RecipeBook.types\";\nimport { ALL, deriveCategories, filterRecipes } from \"./RecipeBook.utils\";\n\n/**\n * Recipe book: search, category filters and a \"craftable only\" switch over a\n * grid of recipes, with the selected recipe's 3 × 3 pattern and result.\n */\nexport const RecipeBook = forwardRef<HTMLDivElement, RecipeBookProps>(function RecipeBook(\n {\n recipes,\n categories,\n value,\n defaultValue,\n onValueChange,\n onCraft,\n defaultCraftableOnly = false,\n label = \"Recipe book\",\n className,\n ...rest\n },\n ref,\n) {\n const titleId = useId();\n const [query, setQuery] = useState(\"\");\n const [category, setCategory] = useState(ALL);\n const [craftableOnly, setCraftableOnly] = useState(defaultCraftableOnly);\n const [selectedId, setSelectedId] = useControllableState({\n value,\n defaultValue: defaultValue ?? \"\",\n onChange: onValueChange,\n });\n\n const filters = [{ id: ALL, label: \"All\" }, ...(categories ?? deriveCategories(recipes))];\n const visible = filterRecipes(recipes, { query, category, craftableOnly });\n const selected = recipes.find((recipe) => recipe.id === selectedId);\n const pattern = Array.from({ length: 9 }, (_, i) => selected?.ingredients?.[i] ?? null);\n\n return (\n <div ref={ref} role=\"group\" aria-label={label} className={cx(styles.book, className)} {...rest}>\n <div className={styles.browser}>\n <BlockInput\n type=\"search\"\n label=\"Search recipes\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n startIcon={<SearchIcon size={16} />}\n />\n <div className={styles.filters}>\n <div role=\"group\" aria-label=\"Categories\" className={styles.categories}>\n {filters.map((filter) => (\n <button\n key={filter.id}\n type=\"button\"\n aria-pressed={category === filter.id}\n className={styles.category}\n onClick={() => setCategory(filter.id)}\n >\n {\"icon\" in filter && filter.icon ? (\n <span className={styles.categoryIcon} aria-hidden=\"true\">\n {filter.icon}\n </span>\n ) : null}\n {filter.label}\n </button>\n ))}\n </div>\n <BlockToggle\n size=\"sm\"\n label=\"Craftable only\"\n checked={craftableOnly}\n onCheckedChange={setCraftableOnly}\n />\n </div>\n <p className={styles.count} role=\"status\">\n {visible.length === 1 ? \"1 recipe\" : `${visible.length} recipes`}\n </p>\n {visible.length > 0 ? (\n <ul className={styles.grid} aria-label=\"Recipes\">\n {visible.map((recipe) => (\n <li key={recipe.id}>\n <InventorySlot\n size=\"md\"\n selected={recipe.id === selectedId}\n label={\n recipe.craftable === false\n ? `${recipe.name} (missing ingredients)`\n : recipe.name\n }\n data-craftable={recipe.craftable === false ? \"false\" : undefined}\n className={styles.recipe}\n onClick={() => setSelectedId(recipe.id)}\n >\n {recipe.result}\n </InventorySlot>\n </li>\n ))}\n </ul>\n ) : (\n <p className={styles.empty}>No recipes match.</p>\n )}\n </div>\n <section className={styles.detail} aria-labelledby={titleId}>\n <h3 id={titleId} className={styles.title}>\n {selected ? selected.name : \"Select a recipe\"}\n </h3>\n {selected ? (\n <>\n <div className={styles.preview}>\n <div role=\"group\" aria-label=\"Pattern\" className={styles.pattern}>\n {pattern.map((cell, index) => (\n <InventorySlot key={index} size=\"sm\">\n {cell}\n </InventorySlot>\n ))}\n </div>\n <span className={styles.arrow} aria-hidden=\"true\">\n <ArrowIcon size={24} />\n </span>\n <InventorySlot size=\"lg\" label={`Makes ${selected.name}`}>\n {selected.result}\n </InventorySlot>\n </div>\n {selected.craftable === false ? (\n <p className={styles.missing}>Missing ingredients</p>\n ) : null}\n {onCraft ? (\n <BlockButton\n variant=\"grass\"\n disabled={selected.craftable === false}\n onClick={() => onCraft(selected.id)}\n >\n Craft\n </BlockButton>\n ) : null}\n </>\n ) : null}\n </section>\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,aAAa,WAA4C,SAAS,WAC7E,EACE,SACA,YACA,OACA,cACA,eACA,SACA,uBAAuB,OACvB,QAAQ,eACR,WACA,GAAG,QAEL,KACA;CACA,MAAM,UAAU,MAAM;CACtB,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CACrC,MAAM,CAAC,UAAU,eAAe,SAAA,KAAY;CAC5C,MAAM,CAAC,eAAe,oBAAoB,SAAS,oBAAoB;CACvE,MAAM,CAAC,YAAY,iBAAiB,qBAAqB;EACvD;EACA,cAAc,gBAAgB;EAC9B,UAAU;CACZ,CAAC;CAED,MAAM,UAAU,CAAC;EAAE,IAAA;EAAS,OAAO;CAAM,GAAG,GAAI,cAAc,iBAAiB,OAAO,CAAE;CACxF,MAAM,UAAU,cAAc,SAAS;EAAE;EAAO;EAAU;CAAc,CAAC;CACzE,MAAM,WAAW,QAAQ,MAAM,WAAW,OAAO,OAAO,UAAU;CAClE,MAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAAM,UAAU,cAAc,MAAM,IAAI;CAEtF,OACE,qBAAC,OAAD;EAAU;EAAK,MAAK;EAAQ,cAAY;EAAO,WAAW,GAAGA,0BAAO,MAAM,SAAS;EAAG,GAAI;EAA1F,UAAA,CACE,qBAAC,OAAD;GAAK,WAAWA,0BAAO;GAAvB,UAAA;IACE,oBAAC,YAAD;KACE,MAAK;KACL,OAAM;KACN,OAAO;KACP,WAAW,UAAU,SAAS,MAAM,OAAO,KAAK;KAChD,WAAW,oBAAC,YAAD,EAAY,MAAM,GAAK,CAAA;IACnC,CAAA;IACD,qBAAC,OAAD;KAAK,WAAWA,0BAAO;KAAvB,UAAA,CACE,oBAAC,OAAD;MAAK,MAAK;MAAQ,cAAW;MAAa,WAAWA,0BAAO;MACzD,UAAA,QAAQ,KAAK,WACZ,qBAAC,UAAD;OAEE,MAAK;OACL,gBAAc,aAAa,OAAO;OAClC,WAAWA,0BAAO;OAClB,eAAe,YAAY,OAAO,EAAE;OALtC,UAAA,CAOG,UAAU,UAAU,OAAO,OAC1B,oBAAC,QAAD;QAAM,WAAWA,0BAAO;QAAc,eAAY;QAC/C,UAAA,OAAO;OACJ,CAAA,IACJ,MACH,OAAO,KACF;MAZD,GAAA,OAAO,EAYN,CACT;KACE,CAAA,GACL,oBAAC,aAAD;MACE,MAAK;MACL,OAAM;MACN,SAAS;MACT,iBAAiB;KAClB,CAAA,CACE;;IACL,oBAAC,KAAD;KAAG,WAAWA,0BAAO;KAAO,MAAK;KAC9B,UAAA,QAAQ,WAAW,IAAI,aAAa,GAAG,QAAQ,OAAO;IACtD,CAAA;IACF,QAAQ,SAAS,IAChB,oBAAC,MAAD;KAAI,WAAWA,0BAAO;KAAM,cAAW;KACpC,UAAA,QAAQ,KAAK,WACZ,oBAAC,MAAD,EAAA,UACE,oBAAC,eAAD;MACE,MAAK;MACL,UAAU,OAAO,OAAO;MACxB,OACE,OAAO,cAAc,QACjB,GAAG,OAAO,KAAK,0BACf,OAAO;MAEb,kBAAgB,OAAO,cAAc,QAAQ,UAAU,KAAA;MACvD,WAAWA,0BAAO;MAClB,eAAe,cAAc,OAAO,EAAE;MAErC,UAAA,OAAO;KACK,CAAA,EACb,GAfK,OAAO,EAeZ,CACL;IACC,CAAA,IAEJ,oBAAC,KAAD;KAAG,WAAWA,0BAAO;KAAO,UAAA;IAAoB,CAAA;GAE/C;EACL,CAAA,GAAA,qBAAC,WAAD;GAAS,WAAWA,0BAAO;GAAQ,mBAAiB;GAApD,UAAA,CACE,oBAAC,MAAD;IAAI,IAAI;IAAS,WAAWA,0BAAO;IAChC,UAAA,WAAW,SAAS,OAAO;GAC1B,CAAA,GACH,WACC,qBAAAC,
|
|
1
|
+
{"version":3,"file":"RecipeBook.js","names":["styles","_Fragment"],"sources":["../../../../src/components/crafting/RecipeBook/RecipeBook.tsx"],"sourcesContent":["import { ArrowIcon, SearchIcon } from \"@malilion/block-ui-icons\";\nimport { forwardRef, useId, useState } from \"react\";\nimport { useControllableState } from \"../../../hooks/useControllableState\";\nimport { cx } from \"../../../utils/cx\";\nimport { BlockButton } from \"../../actions/BlockButton/BlockButton\";\nimport { BlockInput } from \"../../forms/BlockInput/BlockInput\";\nimport { BlockToggle } from \"../../forms/BlockToggle/BlockToggle\";\nimport { InventorySlot } from \"../../inventory/InventorySlot/InventorySlot\";\nimport styles from \"./RecipeBook.module.css\";\nimport type { RecipeBookProps } from \"./RecipeBook.types\";\nimport { ALL, deriveCategories, filterRecipes } from \"./RecipeBook.utils\";\n\n/**\n * Recipe book: search, category filters and a \"craftable only\" switch over a\n * grid of recipes, with the selected recipe's 3 × 3 pattern and result.\n */\nexport const RecipeBook = forwardRef<HTMLDivElement, RecipeBookProps>(function RecipeBook(\n {\n recipes,\n categories,\n value,\n defaultValue,\n onValueChange,\n onCraft,\n defaultCraftableOnly = false,\n label = \"Recipe book\",\n className,\n ...rest\n },\n ref,\n) {\n const titleId = useId();\n const [query, setQuery] = useState(\"\");\n const [category, setCategory] = useState(ALL);\n const [craftableOnly, setCraftableOnly] = useState(defaultCraftableOnly);\n const [selectedId, setSelectedId] = useControllableState({\n value,\n defaultValue: defaultValue ?? \"\",\n onChange: onValueChange,\n });\n\n const filters = [{ id: ALL, label: \"All\" }, ...(categories ?? deriveCategories(recipes))];\n const visible = filterRecipes(recipes, { query, category, craftableOnly });\n const selected = recipes.find((recipe) => recipe.id === selectedId);\n const pattern = Array.from({ length: 9 }, (_, i) => selected?.ingredients?.[i] ?? null);\n\n return (\n <div ref={ref} role=\"group\" aria-label={label} className={cx(styles.book, className)} {...rest}>\n <div className={styles.browser}>\n <BlockInput\n type=\"search\"\n label=\"Search recipes\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n startIcon={<SearchIcon size={16} />}\n />\n <div className={styles.filters}>\n <div role=\"group\" aria-label=\"Categories\" className={styles.categories}>\n {filters.map((filter) => (\n <button\n key={filter.id}\n type=\"button\"\n aria-pressed={category === filter.id}\n className={styles.category}\n onClick={() => setCategory(filter.id)}\n >\n {\"icon\" in filter && filter.icon ? (\n <span className={styles.categoryIcon} aria-hidden=\"true\">\n {filter.icon}\n </span>\n ) : null}\n {filter.label}\n </button>\n ))}\n </div>\n <BlockToggle\n size=\"sm\"\n label=\"Craftable only\"\n checked={craftableOnly}\n onCheckedChange={setCraftableOnly}\n />\n </div>\n <p className={styles.count} role=\"status\">\n {visible.length === 1 ? \"1 recipe\" : `${visible.length} recipes`}\n </p>\n {visible.length > 0 ? (\n <ul className={styles.grid} aria-label=\"Recipes\">\n {visible.map((recipe) => (\n <li key={recipe.id}>\n <InventorySlot\n size=\"md\"\n selected={recipe.id === selectedId}\n label={\n recipe.craftable === false\n ? `${recipe.name} (missing ingredients)`\n : recipe.name\n }\n data-craftable={recipe.craftable === false ? \"false\" : undefined}\n className={styles.recipe}\n onClick={() => setSelectedId(recipe.id)}\n >\n {recipe.result}\n </InventorySlot>\n </li>\n ))}\n </ul>\n ) : (\n <p className={styles.empty}>No recipes match.</p>\n )}\n </div>\n <section className={styles.detail} aria-labelledby={titleId}>\n <h3 id={titleId} className={styles.title}>\n {selected ? selected.name : \"Select a recipe\"}\n </h3>\n {selected ? (\n <>\n <div className={styles.preview}>\n <div role=\"group\" aria-label=\"Pattern\" className={styles.pattern}>\n {pattern.map((cell, index) => (\n <InventorySlot key={index} size=\"sm\">\n {cell}\n </InventorySlot>\n ))}\n </div>\n <span className={styles.arrow} aria-hidden=\"true\">\n <ArrowIcon size={24} />\n </span>\n <InventorySlot size=\"lg\" label={`Makes ${selected.name}`}>\n {selected.result}\n </InventorySlot>\n </div>\n {selected.craftable === false ? (\n <p className={styles.missing}>Missing ingredients</p>\n ) : null}\n {onCraft ? (\n <BlockButton\n variant=\"grass\"\n disabled={selected.craftable === false}\n onClick={() => onCraft(selected.id)}\n >\n Craft\n </BlockButton>\n ) : null}\n </>\n ) : null}\n </section>\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { KbdProps } from "./Kbd.types";
|
|
2
|
+
/** Pixel keycap for keyboard hints ("Press E to open your inventory"). Combinations use nested `<kbd>`s. */
|
|
3
|
+
export declare const Kbd: import("react").ForwardRefExoticComponent<KbdProps & import("react").RefAttributes<HTMLElement>>;
|
|
4
|
+
//# sourceMappingURL=Kbd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Kbd.d.ts","sourceRoot":"","sources":["../../../../src/components/display/Kbd/Kbd.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,4GAA4G;AAC5G,eAAO,MAAM,GAAG,kGAyBd,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cx } from "../../../utils/cx.js";
|
|
2
|
+
import Kbd_module_default from "./Kbd.module.js";
|
|
3
|
+
import { Fragment, forwardRef } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/display/Kbd/Kbd.tsx
|
|
6
|
+
/** Pixel keycap for keyboard hints ("Press E to open your inventory"). Combinations use nested `<kbd>`s. */
|
|
7
|
+
var Kbd = forwardRef(function Kbd({ keys, children, size = "md", className, ...rest }, ref) {
|
|
8
|
+
if (keys && keys.length > 1) return /* @__PURE__ */ jsx("kbd", {
|
|
9
|
+
ref,
|
|
10
|
+
"data-size": size,
|
|
11
|
+
className: cx(Kbd_module_default.combo, className),
|
|
12
|
+
...rest,
|
|
13
|
+
children: keys.map((key, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 ? /* @__PURE__ */ jsx("span", {
|
|
14
|
+
className: Kbd_module_default.plus,
|
|
15
|
+
"aria-hidden": "true",
|
|
16
|
+
children: "+"
|
|
17
|
+
}) : null, /* @__PURE__ */ jsx("kbd", {
|
|
18
|
+
className: Kbd_module_default.key,
|
|
19
|
+
children: key
|
|
20
|
+
})] }, `${key}-${index}`))
|
|
21
|
+
});
|
|
22
|
+
return /* @__PURE__ */ jsx("kbd", {
|
|
23
|
+
ref,
|
|
24
|
+
"data-size": size,
|
|
25
|
+
className: cx(Kbd_module_default.key, className),
|
|
26
|
+
...rest,
|
|
27
|
+
children: keys?.[0] ?? children
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
//#endregion
|
|
31
|
+
export { Kbd };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=Kbd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Kbd.js","names":["styles"],"sources":["../../../../src/components/display/Kbd/Kbd.tsx"],"sourcesContent":["import { Fragment, forwardRef } from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport styles from \"./Kbd.module.css\";\nimport type { KbdProps } from \"./Kbd.types\";\n\n/** Pixel keycap for keyboard hints (\"Press E to open your inventory\"). Combinations use nested `<kbd>`s. */\nexport const Kbd = forwardRef<HTMLElement, KbdProps>(function Kbd(\n { keys, children, size = \"md\", className, ...rest },\n ref,\n) {\n if (keys && keys.length > 1) {\n return (\n <kbd ref={ref} data-size={size} className={cx(styles.combo, className)} {...rest}>\n {keys.map((key, index) => (\n <Fragment key={`${key}-${index}`}>\n {index > 0 ? (\n <span className={styles.plus} aria-hidden=\"true\">\n +\n </span>\n ) : null}\n <kbd className={styles.key}>{key}</kbd>\n </Fragment>\n ))}\n </kbd>\n );\n }\n return (\n <kbd ref={ref} data-size={size} className={cx(styles.key, className)} {...rest}>\n {keys?.[0] ?? children}\n </kbd>\n );\n});\n"],"mappings":";;;;;;AAMA,IAAa,MAAM,WAAkC,SAAS,IAC5D,EAAE,MAAM,UAAU,OAAO,MAAM,WAAW,GAAG,QAC7C,KACA;CACA,IAAI,QAAQ,KAAK,SAAS,GACxB,OACE,oBAAC,OAAD;EAAU;EAAK,aAAW;EAAM,WAAW,GAAGA,mBAAO,OAAO,SAAS;EAAG,GAAI;EACzE,UAAA,KAAK,KAAK,KAAK,UACd,qBAAC,UAAD,EAAA,UAAA,CACG,QAAQ,IACP,oBAAC,QAAD;GAAM,WAAWA,mBAAO;GAAM,eAAY;GAAO,UAAA;EAE3C,CAAA,IACJ,MACJ,oBAAC,OAAD;GAAK,WAAWA,mBAAO;GAAM,UAAA;EAAS,CAAA,CAC9B,EAAA,GAPK,GAAG,IAAI,GAAG,OAOf,CACX;CACE,CAAA;CAGT,OACE,oBAAC,OAAD;EAAU;EAAK,aAAW;EAAM,WAAW,GAAGA,mBAAO,KAAK,SAAS;EAAG,GAAI;EACvE,UAAA,OAAO,MAAM;CACX,CAAA;AAET,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/components/display/Kbd/Kbd.module.css
|
|
2
|
+
var key = "block-key-FLsly";
|
|
3
|
+
var combo = "block-combo-xCqO-";
|
|
4
|
+
var plus = "block-plus-OaCLf";
|
|
5
|
+
var Kbd_module_default = {
|
|
6
|
+
key,
|
|
7
|
+
combo,
|
|
8
|
+
plus
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { combo, Kbd_module_default as default, key, plus };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=Kbd.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Kbd.module.js","names":[],"sources":["../../../../src/components/display/Kbd/Kbd.module.css"],"sourcesContent":[".key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n min-height: 28px;\n padding: 0 var(--block-space-1);\n font-family: var(--block-font-display);\n font-size: var(--block-font-size-xs);\n font-weight: var(--block-font-weight-bold);\n line-height: 1;\n text-transform: uppercase;\n white-space: nowrap;\n color: var(--block-text);\n background-color: var(--block-surface-alt);\n border: var(--block-border-width-thin) solid var(--block-border);\n box-shadow:\n inset 2px 2px 0 var(--block-shadow-bevel-light),\n inset -2px -4px 0 var(--block-shadow-bevel-dark);\n padding-bottom: 2px;\n vertical-align: middle;\n}\n\n.key[data-size=\"sm\"],\n[data-size=\"sm\"] > .key {\n min-width: 22px;\n min-height: 22px;\n font-size: 0.625rem;\n}\n\n.combo {\n display: inline-flex;\n align-items: center;\n gap: var(--block-space-1);\n font: inherit;\n vertical-align: middle;\n}\n\n.plus {\n font-family: var(--block-font-display);\n font-size: var(--block-font-size-xs);\n color: var(--block-text-muted);\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export interface KbdProps extends HTMLAttributes<HTMLElement> {
|
|
3
|
+
/** A single key (`children`) or a combination, e.g. `["Ctrl", "Shift", "C"]`. */
|
|
4
|
+
keys?: string[];
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
/** @default "md" */
|
|
7
|
+
size?: "sm" | "md";
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=Kbd.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Kbd.types.d.ts","sourceRoot":"","sources":["../../../../src/components/display/Kbd/Kbd.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD,MAAM,WAAW,QAAS,SAAQ,cAAc,CAAC,WAAW,CAAC;IAC3D,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,oBAAoB;IACpB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/display/Kbd/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PopoverProps } from "./Popover.types";
|
|
2
|
+
/**
|
|
3
|
+
* Click-to-open floating panel for interactive content (a non-modal dialog).
|
|
4
|
+
* Focus moves into it when it opens; Escape, an outside click or tabbing out
|
|
5
|
+
* closes it, and Escape returns focus to the trigger.
|
|
6
|
+
*/
|
|
7
|
+
export declare const Popover: import("react").ForwardRefExoticComponent<PopoverProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
8
|
+
//# sourceMappingURL=Popover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../../../src/components/display/Popover/Popover.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,OAAO,0GAgHlB,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { cx } from "../../../utils/cx.js";
|
|
2
|
+
import { usePortalContainer } from "../../../provider/context.js";
|
|
3
|
+
import { useAnchoredPosition } from "../../../hooks/useAnchoredPosition.js";
|
|
4
|
+
import { mergeRefs } from "../../../utils/refs.js";
|
|
5
|
+
import { useControllableState } from "../../../hooks/useControllableState.js";
|
|
6
|
+
import { getFocusable } from "../../../utils/dom.js";
|
|
7
|
+
import Popover_module_default from "./Popover.module.js";
|
|
8
|
+
import { cloneElement, forwardRef, useEffect, useId, useRef } from "react";
|
|
9
|
+
import { createPortal } from "react-dom";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
//#region src/components/display/Popover/Popover.tsx
|
|
12
|
+
/**
|
|
13
|
+
* Click-to-open floating panel for interactive content (a non-modal dialog).
|
|
14
|
+
* Focus moves into it when it opens; Escape, an outside click or tabbing out
|
|
15
|
+
* closes it, and Escape returns focus to the trigger.
|
|
16
|
+
*/
|
|
17
|
+
var Popover = forwardRef(function Popover({ content, children, title, label, placement = "bottom", align = "center", open, defaultOpen = false, onOpenChange, className, ...rest }, ref) {
|
|
18
|
+
const baseId = useId();
|
|
19
|
+
const panelId = `${baseId}-panel`;
|
|
20
|
+
const titleId = `${baseId}-title`;
|
|
21
|
+
const container = usePortalContainer();
|
|
22
|
+
const anchorRef = useRef(null);
|
|
23
|
+
const panelRef = useRef(null);
|
|
24
|
+
const [isOpen, setOpen] = useControllableState({
|
|
25
|
+
value: open,
|
|
26
|
+
defaultValue: defaultOpen,
|
|
27
|
+
onChange: onOpenChange
|
|
28
|
+
});
|
|
29
|
+
const position = useAnchoredPosition(anchorRef, panelRef, isOpen, {
|
|
30
|
+
side: placement,
|
|
31
|
+
align
|
|
32
|
+
});
|
|
33
|
+
const isInside = (node) => Boolean(node && (anchorRef.current?.contains(node) || panelRef.current?.contains(node)));
|
|
34
|
+
const triggerElement = () => anchorRef.current?.querySelector("button, [role='button'], a[href]");
|
|
35
|
+
const close = (returnFocus) => {
|
|
36
|
+
setOpen(false);
|
|
37
|
+
if (returnFocus) triggerElement()?.focus();
|
|
38
|
+
};
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!isOpen || !position) return;
|
|
41
|
+
const panel = panelRef.current;
|
|
42
|
+
if (panel && !panel.contains(document.activeElement)) (getFocusable(panel)[0] ?? panel).focus();
|
|
43
|
+
}, [isOpen, position !== null]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!isOpen) return;
|
|
46
|
+
const onPointerDown = (event) => {
|
|
47
|
+
if (!isInside(event.target)) close(false);
|
|
48
|
+
};
|
|
49
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
50
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
51
|
+
}, [isOpen]);
|
|
52
|
+
const onPanelKeyDown = (event) => {
|
|
53
|
+
if (event.key === "Escape") {
|
|
54
|
+
event.stopPropagation();
|
|
55
|
+
close(true);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const onPanelBlur = (event) => {
|
|
59
|
+
const next = event.relatedTarget;
|
|
60
|
+
if (next && !isInside(next)) close(false);
|
|
61
|
+
};
|
|
62
|
+
const trigger = cloneElement(children, {
|
|
63
|
+
"aria-haspopup": "dialog",
|
|
64
|
+
"aria-expanded": isOpen,
|
|
65
|
+
"aria-controls": isOpen ? panelId : void 0,
|
|
66
|
+
onClick: (event) => {
|
|
67
|
+
children.props.onClick?.(event);
|
|
68
|
+
setOpen(!isOpen);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
72
|
+
ref: mergeRefs(ref, anchorRef),
|
|
73
|
+
className: cx(Popover_module_default.anchor, className),
|
|
74
|
+
...rest,
|
|
75
|
+
children: [trigger, container && isOpen ? createPortal(/* @__PURE__ */ jsxs("div", {
|
|
76
|
+
ref: panelRef,
|
|
77
|
+
id: panelId,
|
|
78
|
+
role: "dialog",
|
|
79
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
80
|
+
"aria-label": title ? void 0 : label,
|
|
81
|
+
tabIndex: -1,
|
|
82
|
+
"data-placement": position?.side ?? placement,
|
|
83
|
+
className: Popover_module_default.panel,
|
|
84
|
+
style: position?.style,
|
|
85
|
+
onKeyDown: onPanelKeyDown,
|
|
86
|
+
onBlur: onPanelBlur,
|
|
87
|
+
children: [title ? /* @__PURE__ */ jsx("p", {
|
|
88
|
+
id: titleId,
|
|
89
|
+
className: Popover_module_default.title,
|
|
90
|
+
children: title
|
|
91
|
+
}) : null, /* @__PURE__ */ jsx("div", {
|
|
92
|
+
className: Popover_module_default.body,
|
|
93
|
+
children: content
|
|
94
|
+
})]
|
|
95
|
+
}), container) : null]
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
//#endregion
|
|
99
|
+
export { Popover };
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=Popover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popover.js","names":["styles"],"sources":["../../../../src/components/display/Popover/Popover.tsx"],"sourcesContent":["import {\n cloneElement,\n forwardRef,\n useEffect,\n useId,\n useRef,\n type FocusEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useAnchoredPosition } from \"../../../hooks/useAnchoredPosition\";\nimport { useControllableState } from \"../../../hooks/useControllableState\";\nimport { usePortalContainer } from \"../../../provider/context\";\nimport { cx } from \"../../../utils/cx\";\nimport { getFocusable } from \"../../../utils/dom\";\nimport { mergeRefs } from \"../../../utils/refs\";\nimport styles from \"./Popover.module.css\";\nimport type { PopoverProps } from \"./Popover.types\";\n\n/**\n * Click-to-open floating panel for interactive content (a non-modal dialog).\n * Focus moves into it when it opens; Escape, an outside click or tabbing out\n * closes it, and Escape returns focus to the trigger.\n */\nexport const Popover = forwardRef<HTMLSpanElement, PopoverProps>(function Popover(\n {\n content,\n children,\n title,\n label,\n placement = \"bottom\",\n align = \"center\",\n open,\n defaultOpen = false,\n onOpenChange,\n className,\n ...rest\n },\n ref,\n) {\n const baseId = useId();\n const panelId = `${baseId}-panel`;\n const titleId = `${baseId}-title`;\n const container = usePortalContainer();\n const anchorRef = useRef<HTMLSpanElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const [isOpen, setOpen] = useControllableState({\n value: open,\n defaultValue: defaultOpen,\n onChange: onOpenChange,\n });\n const position = useAnchoredPosition(anchorRef, panelRef, isOpen, { side: placement, align });\n\n const isInside = (node: Node | null) =>\n Boolean(node && (anchorRef.current?.contains(node) || panelRef.current?.contains(node)));\n\n const triggerElement = () =>\n anchorRef.current?.querySelector<HTMLElement>(\"button, [role='button'], a[href]\");\n\n const close = (returnFocus: boolean) => {\n setOpen(false);\n if (returnFocus) triggerElement()?.focus();\n };\n\n // Move focus into the panel once it is open and positioned.\n useEffect(() => {\n if (!isOpen || !position) return;\n const panel = panelRef.current;\n if (panel && !panel.contains(document.activeElement)) (getFocusable(panel)[0] ?? panel).focus();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, position !== null]);\n\n useEffect(() => {\n if (!isOpen) return;\n const onPointerDown = (event: PointerEvent) => {\n if (!isInside(event.target as Node)) close(false);\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen]);\n\n const onPanelKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n close(true);\n }\n };\n\n const onPanelBlur = (event: FocusEvent<HTMLDivElement>) => {\n const next = event.relatedTarget as Node | null;\n if (next && !isInside(next)) close(false);\n };\n\n const trigger = cloneElement(children, {\n \"aria-haspopup\": \"dialog\",\n \"aria-expanded\": isOpen,\n \"aria-controls\": isOpen ? panelId : undefined,\n onClick: (event: never) => {\n children.props.onClick?.(event);\n setOpen(!isOpen);\n },\n });\n\n return (\n <span ref={mergeRefs(ref, anchorRef)} className={cx(styles.anchor, className)} {...rest}>\n {trigger}\n {container && isOpen\n ? createPortal(\n // A dialog closes on Escape and when focus leaves it (WAI-ARIA dialog pattern).\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions\n <div\n ref={panelRef}\n id={panelId}\n role=\"dialog\"\n aria-labelledby={title ? titleId : undefined}\n aria-label={title ? undefined : label}\n tabIndex={-1}\n data-placement={position?.side ?? placement}\n className={styles.panel}\n style={position?.style}\n onKeyDown={onPanelKeyDown}\n onBlur={onPanelBlur}\n >\n {title ? (\n <p id={titleId} className={styles.title}>\n {title}\n </p>\n ) : null}\n <div className={styles.body}>{content}</div>\n </div>,\n container,\n )\n : null}\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;AAwBA,IAAa,UAAU,WAA0C,SAAS,QACxE,EACE,SACA,UACA,OACA,OACA,YAAY,UACZ,QAAQ,UACR,MACA,cAAc,OACd,cACA,WACA,GAAG,QAEL,KACA;CACA,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,GAAG,OAAO;CAC1B,MAAM,UAAU,GAAG,OAAO;CAC1B,MAAM,YAAY,mBAAmB;CACrC,MAAM,YAAY,OAAwB,IAAI;CAC9C,MAAM,WAAW,OAAuB,IAAI;CAC5C,MAAM,CAAC,QAAQ,WAAW,qBAAqB;EAC7C,OAAO;EACP,cAAc;EACd,UAAU;CACZ,CAAC;CACD,MAAM,WAAW,oBAAoB,WAAW,UAAU,QAAQ;EAAE,MAAM;EAAW;CAAM,CAAC;CAE5F,MAAM,YAAY,SAChB,QAAQ,SAAS,UAAU,SAAS,SAAS,IAAI,KAAK,SAAS,SAAS,SAAS,IAAI,EAAE;CAEzF,MAAM,uBACJ,UAAU,SAAS,cAA2B,kCAAkC;CAElF,MAAM,SAAS,gBAAyB;EACtC,QAAQ,KAAK;EACb,IAAI,aAAa,eAAe,CAAC,EAAE,MAAM;CAC3C;CAGA,gBAAgB;EACd,IAAI,CAAC,UAAU,CAAC,UAAU;EAC1B,MAAM,QAAQ,SAAS;EACvB,IAAI,SAAS,CAAC,MAAM,SAAS,SAAS,aAAa,GAAG,CAAC,aAAa,KAAK,CAAC,CAAC,MAAM,MAAA,CAAO,MAAM;CAEhG,GAAG,CAAC,QAAQ,aAAa,IAAI,CAAC;CAE9B,gBAAgB;EACd,IAAI,CAAC,QAAQ;EACb,MAAM,iBAAiB,UAAwB;GAC7C,IAAI,CAAC,SAAS,MAAM,MAAc,GAAG,MAAM,KAAK;EAClD;EACA,SAAS,iBAAiB,eAAe,aAAa;EACtD,aAAa,SAAS,oBAAoB,eAAe,aAAa;CAExE,GAAG,CAAC,MAAM,CAAC;CAEX,MAAM,kBAAkB,UAAyC;EAC/D,IAAI,MAAM,QAAQ,UAAU;GAC1B,MAAM,gBAAgB;GACtB,MAAM,IAAI;EACZ;CACF;CAEA,MAAM,eAAe,UAAsC;EACzD,MAAM,OAAO,MAAM;EACnB,IAAI,QAAQ,CAAC,SAAS,IAAI,GAAG,MAAM,KAAK;CAC1C;CAEA,MAAM,UAAU,aAAa,UAAU;EACrC,iBAAiB;EACjB,iBAAiB;EACjB,iBAAiB,SAAS,UAAU,KAAA;EACpC,UAAU,UAAiB;GACzB,SAAS,MAAM,UAAU,KAAK;GAC9B,QAAQ,CAAC,MAAM;EACjB;CACF,CAAC;CAED,OACE,qBAAC,QAAD;EAAM,KAAK,UAAU,KAAK,SAAS;EAAG,WAAW,GAAGA,uBAAO,QAAQ,SAAS;EAAG,GAAI;EAAnF,UAAA,CACG,SACA,aAAa,SACV,aAGE,qBAAC,OAAD;GACE,KAAK;GACL,IAAI;GACJ,MAAK;GACL,mBAAiB,QAAQ,UAAU,KAAA;GACnC,cAAY,QAAQ,KAAA,IAAY;GAChC,UAAU;GACV,kBAAgB,UAAU,QAAQ;GAClC,WAAWA,uBAAO;GAClB,OAAO,UAAU;GACjB,WAAW;GACX,QAAQ;GAXV,UAAA,CAaG,QACC,oBAAC,KAAD;IAAG,IAAI;IAAS,WAAWA,uBAAO;IAC/B,UAAA;GACA,CAAA,IACD,MACJ,oBAAC,OAAD;IAAK,WAAWA,uBAAO;IAAO,UAAA;GAAa,CAAA,CACxC;EACL,CAAA,GAAA,SACF,IACA,IACA;;AAEV,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/components/display/Popover/Popover.module.css
|
|
2
|
+
var anchor = "block-anchor-Hc2xy";
|
|
3
|
+
var panel = "block-panel-hIYiA";
|
|
4
|
+
var title = "block-title-0MX0a";
|
|
5
|
+
var body = "block-body-K6cco";
|
|
6
|
+
var Popover_module_default = {
|
|
7
|
+
anchor,
|
|
8
|
+
panel,
|
|
9
|
+
title,
|
|
10
|
+
body
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { anchor, body, Popover_module_default as default, panel, title };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=Popover.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popover.module.js","names":[],"sources":["../../../../src/components/display/Popover/Popover.module.css"],"sourcesContent":[".anchor {\n display: inline-flex;\n vertical-align: middle;\n}\n\n.panel {\n position: fixed;\n top: var(--block-float-y, 0);\n left: var(--block-float-x, 0);\n z-index: var(--block-z-dropdown);\n display: flex;\n flex-direction: column;\n gap: var(--block-space-2);\n width: max-content;\n min-width: 200px;\n max-width: min(360px, calc(100vw - var(--block-space-4)));\n max-height: calc(100vh - var(--block-space-4));\n padding: var(--block-space-3);\n overflow-y: auto;\n color: var(--block-text);\n background-color: var(--block-surface);\n border: var(--block-border-width) solid var(--block-border);\n box-shadow: var(--block-shadow-bevel), var(--block-shadow-drop);\n}\n\n.panel:focus-visible {\n outline: 3px solid var(--block-focus);\n outline-offset: 2px;\n}\n\n.title {\n margin: 0;\n font-family: var(--block-font-display);\n font-size: var(--block-font-size-sm);\n font-weight: var(--block-font-weight-bold);\n letter-spacing: var(--block-letter-spacing-wide);\n text-transform: uppercase;\n text-shadow: var(--block-shadow-text);\n}\n\n.body {\n font-size: var(--block-font-size-sm);\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactElement, ReactNode } from "react";
|
|
2
|
+
export interface PopoverProps extends Omit<HTMLAttributes<HTMLSpanElement>, "content" | "title"> {
|
|
3
|
+
/** Panel content — may contain buttons, links and form fields. */
|
|
4
|
+
content: ReactNode;
|
|
5
|
+
/** A single button that toggles the popover. It receives `aria-expanded` / `aria-controls`. */
|
|
6
|
+
children: ReactElement<{
|
|
7
|
+
"aria-expanded"?: boolean;
|
|
8
|
+
"aria-controls"?: string;
|
|
9
|
+
"aria-haspopup"?: "dialog";
|
|
10
|
+
onClick?: (event: never) => void;
|
|
11
|
+
}>;
|
|
12
|
+
/** Heading of the panel and its accessible name. */
|
|
13
|
+
title?: ReactNode;
|
|
14
|
+
/** Accessible name when there is no `title`. */
|
|
15
|
+
label?: string;
|
|
16
|
+
/** Preferred side; flips when there is no room. @default "bottom" */
|
|
17
|
+
placement?: "top" | "bottom" | "left" | "right";
|
|
18
|
+
/** Alignment along the trigger edge. @default "center" */
|
|
19
|
+
align?: "start" | "center" | "end";
|
|
20
|
+
/** Controlled open state. */
|
|
21
|
+
open?: boolean;
|
|
22
|
+
/** Initial open state when uncontrolled. @default false */
|
|
23
|
+
defaultOpen?: boolean;
|
|
24
|
+
onOpenChange?: (open: boolean) => void;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=Popover.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popover.types.d.ts","sourceRoot":"","sources":["../../../../src/components/display/Popover/Popover.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErE,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9F,kEAAkE;IAClE,OAAO,EAAE,SAAS,CAAC;IACnB,+FAA+F;IAC/F,QAAQ,EAAE,YAAY,CAAC;QACrB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,QAAQ,CAAC;QAC3B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;KAClC,CAAC,CAAC;IACH,oDAAoD;IACpD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAChD,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,6BAA6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/display/Popover/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { BlockButton } from "../../actions/BlockButton/BlockButton.js";
|
|
|
3
3
|
import SkillTree_module_default from "./SkillTree.module.js";
|
|
4
4
|
import { gridSize, skillState } from "./SkillTree.utils.js";
|
|
5
5
|
import { forwardRef, useId, useState } from "react";
|
|
6
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/display/SkillTree/SkillTree.tsx
|
|
8
8
|
var STATE_TEXT = {
|
|
9
9
|
locked: "locked",
|
|
@@ -99,7 +99,7 @@ var SkillTree = forwardRef(function SkillTree({ skills, unlocked, points, onUnlo
|
|
|
99
99
|
className: SkillTree_module_default.detail,
|
|
100
100
|
"aria-live": "polite",
|
|
101
101
|
"aria-label": "Skill details",
|
|
102
|
-
children: selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
102
|
+
children: selected ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
103
103
|
/* @__PURE__ */ jsxs("p", {
|
|
104
104
|
className: SkillTree_module_default.name,
|
|
105
105
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SkillTree.js","names":["styles","_Fragment"],"sources":["../../../../src/components/display/SkillTree/SkillTree.tsx"],"sourcesContent":["import { forwardRef, useId, useState, type CSSProperties } from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport { BlockButton } from \"../../actions/BlockButton/BlockButton\";\nimport styles from \"./SkillTree.module.css\";\nimport type { SkillTreeProps } from \"./SkillTree.types\";\nimport { gridSize, skillState } from \"./SkillTree.utils\";\n\nconst STATE_TEXT = { locked: \"locked\", available: \"available\", unlocked: \"unlocked\" } as const;\n\n/**\n * Skill tree: nodes on a grid joined to their prerequisites. Unlocked skills\n * glow, available ones can be bought with skill points, locked ones show what\n * they need. Selecting a node shows its details and Unlock button.\n */\nexport const SkillTree = forwardRef<HTMLDivElement, SkillTreeProps>(function SkillTree(\n { skills, unlocked, points, onUnlock, defaultValue, label = \"Skill tree\", className, ...rest },\n ref,\n) {\n const detailId = useId();\n const [selectedId, setSelectedId] = useState(defaultValue ?? skills[0]?.id ?? \"\");\n const unlockedSet = new Set(unlocked);\n const byId = new Map(skills.map((skill) => [skill.id, skill]));\n const { rows, columns } = gridSize(skills);\n const selected = byId.get(selectedId);\n const selectedState = selected ? skillState(selected, unlockedSet) : undefined;\n const selectedCost = selected?.cost ?? 1;\n const affordable = points === undefined || points >= selectedCost;\n const missing =\n selected && selectedState === \"locked\"\n ? (selected.requires ?? [])\n .filter((id) => !unlockedSet.has(id))\n .map((id) => byId.get(id)?.label ?? id)\n : [];\n\n // Connector centres in grid units (0.5 = middle of the first cell).\n const edges = skills.flatMap((skill) =>\n (skill.requires ?? []).flatMap((id) => {\n const from = byId.get(id);\n return from\n ? [{ key: `${id}-${skill.id}`, from, to: skill, lit: unlockedSet.has(skill.id) }]\n : [];\n }),\n );\n\n return (\n <div ref={ref} role=\"group\" aria-label={label} className={cx(styles.tree, className)} {...rest}>\n {points !== undefined ? (\n <p className={styles.points}>\n Skill points: <strong>{points}</strong>\n </p>\n ) : null}\n <div\n className={styles.board}\n style={{ \"--block-skill-rows\": rows, \"--block-skill-columns\": columns } as CSSProperties}\n >\n <svg\n className={styles.lines}\n viewBox={`0 0 ${columns} ${rows}`}\n preserveAspectRatio=\"none\"\n aria-hidden=\"true\"\n >\n {edges.map(({ key, from, to, lit }) => (\n <line\n key={key}\n x1={from.column - 0.5}\n y1={from.row - 0.5}\n x2={to.column - 0.5}\n y2={to.row - 0.5}\n data-lit={lit || undefined}\n vectorEffect=\"non-scaling-stroke\"\n />\n ))}\n </svg>\n <ul className={styles.nodes} aria-label=\"Skills\">\n {skills.map((skill) => {\n const state = skillState(skill, unlockedSet);\n return (\n <li\n key={skill.id}\n className={styles.cell}\n style={\n {\n \"--block-skill-row\": skill.row,\n \"--block-skill-col\": skill.column,\n } as CSSProperties\n }\n >\n <button\n type=\"button\"\n className={styles.node}\n data-state={state}\n aria-pressed={skill.id === selectedId}\n aria-controls={detailId}\n aria-label={`${skill.label}, ${STATE_TEXT[state]}`}\n onClick={() => setSelectedId(skill.id)}\n >\n <span className={styles.icon} aria-hidden=\"true\">\n {skill.icon}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n <section\n id={detailId}\n className={styles.detail}\n aria-live=\"polite\"\n aria-label=\"Skill details\"\n >\n {selected ? (\n <>\n <p className={styles.name}>\n {selected.label}{\" \"}\n <span className={styles.state} data-state={selectedState}>\n {selectedState}\n </span>\n </p>\n {selected.description ? (\n <p className={styles.description}>{selected.description}</p>\n ) : null}\n {missing.length > 0 ? (\n <p className={styles.requires}>Requires: {missing.join(\", \")}</p>\n ) : null}\n {selectedState === \"available\" && onUnlock ? (\n <BlockButton\n variant=\"emerald\"\n size=\"sm\"\n disabled={!affordable}\n onClick={() => onUnlock(selected.id)}\n >\n {affordable\n ? `Unlock (${selectedCost} ${selectedCost === 1 ? \"point\" : \"points\"})`\n : `Needs ${selectedCost} ${selectedCost === 1 ? \"point\" : \"points\"}`}\n </BlockButton>\n ) : null}\n </>\n ) : null}\n </section>\n </div>\n );\n});\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"SkillTree.js","names":["styles","_Fragment"],"sources":["../../../../src/components/display/SkillTree/SkillTree.tsx"],"sourcesContent":["import { forwardRef, useId, useState, type CSSProperties } from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport { BlockButton } from \"../../actions/BlockButton/BlockButton\";\nimport styles from \"./SkillTree.module.css\";\nimport type { SkillTreeProps } from \"./SkillTree.types\";\nimport { gridSize, skillState } from \"./SkillTree.utils\";\n\nconst STATE_TEXT = { locked: \"locked\", available: \"available\", unlocked: \"unlocked\" } as const;\n\n/**\n * Skill tree: nodes on a grid joined to their prerequisites. Unlocked skills\n * glow, available ones can be bought with skill points, locked ones show what\n * they need. Selecting a node shows its details and Unlock button.\n */\nexport const SkillTree = forwardRef<HTMLDivElement, SkillTreeProps>(function SkillTree(\n { skills, unlocked, points, onUnlock, defaultValue, label = \"Skill tree\", className, ...rest },\n ref,\n) {\n const detailId = useId();\n const [selectedId, setSelectedId] = useState(defaultValue ?? skills[0]?.id ?? \"\");\n const unlockedSet = new Set(unlocked);\n const byId = new Map(skills.map((skill) => [skill.id, skill]));\n const { rows, columns } = gridSize(skills);\n const selected = byId.get(selectedId);\n const selectedState = selected ? skillState(selected, unlockedSet) : undefined;\n const selectedCost = selected?.cost ?? 1;\n const affordable = points === undefined || points >= selectedCost;\n const missing =\n selected && selectedState === \"locked\"\n ? (selected.requires ?? [])\n .filter((id) => !unlockedSet.has(id))\n .map((id) => byId.get(id)?.label ?? id)\n : [];\n\n // Connector centres in grid units (0.5 = middle of the first cell).\n const edges = skills.flatMap((skill) =>\n (skill.requires ?? []).flatMap((id) => {\n const from = byId.get(id);\n return from\n ? [{ key: `${id}-${skill.id}`, from, to: skill, lit: unlockedSet.has(skill.id) }]\n : [];\n }),\n );\n\n return (\n <div ref={ref} role=\"group\" aria-label={label} className={cx(styles.tree, className)} {...rest}>\n {points !== undefined ? (\n <p className={styles.points}>\n Skill points: <strong>{points}</strong>\n </p>\n ) : null}\n <div\n className={styles.board}\n style={{ \"--block-skill-rows\": rows, \"--block-skill-columns\": columns } as CSSProperties}\n >\n <svg\n className={styles.lines}\n viewBox={`0 0 ${columns} ${rows}`}\n preserveAspectRatio=\"none\"\n aria-hidden=\"true\"\n >\n {edges.map(({ key, from, to, lit }) => (\n <line\n key={key}\n x1={from.column - 0.5}\n y1={from.row - 0.5}\n x2={to.column - 0.5}\n y2={to.row - 0.5}\n data-lit={lit || undefined}\n vectorEffect=\"non-scaling-stroke\"\n />\n ))}\n </svg>\n <ul className={styles.nodes} aria-label=\"Skills\">\n {skills.map((skill) => {\n const state = skillState(skill, unlockedSet);\n return (\n <li\n key={skill.id}\n className={styles.cell}\n style={\n {\n \"--block-skill-row\": skill.row,\n \"--block-skill-col\": skill.column,\n } as CSSProperties\n }\n >\n <button\n type=\"button\"\n className={styles.node}\n data-state={state}\n aria-pressed={skill.id === selectedId}\n aria-controls={detailId}\n aria-label={`${skill.label}, ${STATE_TEXT[state]}`}\n onClick={() => setSelectedId(skill.id)}\n >\n <span className={styles.icon} aria-hidden=\"true\">\n {skill.icon}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n <section\n id={detailId}\n className={styles.detail}\n aria-live=\"polite\"\n aria-label=\"Skill details\"\n >\n {selected ? (\n <>\n <p className={styles.name}>\n {selected.label}{\" \"}\n <span className={styles.state} data-state={selectedState}>\n {selectedState}\n </span>\n </p>\n {selected.description ? (\n <p className={styles.description}>{selected.description}</p>\n ) : null}\n {missing.length > 0 ? (\n <p className={styles.requires}>Requires: {missing.join(\", \")}</p>\n ) : null}\n {selectedState === \"available\" && onUnlock ? (\n <BlockButton\n variant=\"emerald\"\n size=\"sm\"\n disabled={!affordable}\n onClick={() => onUnlock(selected.id)}\n >\n {affordable\n ? `Unlock (${selectedCost} ${selectedCost === 1 ? \"point\" : \"points\"})`\n : `Needs ${selectedCost} ${selectedCost === 1 ? \"point\" : \"points\"}`}\n </BlockButton>\n ) : null}\n </>\n ) : null}\n </section>\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,OAAO,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BlockButton } from "../../actions/BlockButton/BlockButton.js";
|
|
2
2
|
import { BlockModal } from "../Modal/BlockModal.js";
|
|
3
3
|
import { useRef } from "react";
|
|
4
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/feedback/ConfirmDialog/ConfirmDialog.tsx
|
|
6
6
|
/**
|
|
7
7
|
* Confirmation built on `BlockModal` with `role="alertdialog"`. For the
|
|
@@ -25,7 +25,7 @@ function ConfirmDialog({ open, title, description, confirmText = "Confirm", canc
|
|
|
25
25
|
initialFocusRef: danger ? cancelRef : confirmRef,
|
|
26
26
|
className,
|
|
27
27
|
"data-variant": variant,
|
|
28
|
-
footer: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(BlockButton, {
|
|
28
|
+
footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(BlockButton, {
|
|
29
29
|
ref: cancelRef,
|
|
30
30
|
variant: "stone",
|
|
31
31
|
onClick: onCancel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfirmDialog.js","names":["_Fragment"],"sources":["../../../../src/components/feedback/ConfirmDialog/ConfirmDialog.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport { BlockButton } from \"../../actions/BlockButton/BlockButton\";\nimport { BlockModal } from \"../Modal/BlockModal\";\nimport type { ConfirmDialogProps } from \"./ConfirmDialog.types\";\n\n/**\n * Confirmation built on `BlockModal` with `role=\"alertdialog\"`. For the\n * `danger` variant the safe action (Cancel) receives focus first.\n */\nexport function ConfirmDialog({\n open,\n title,\n description,\n confirmText = \"Confirm\",\n cancelText = \"Cancel\",\n variant = \"default\",\n onConfirm,\n onCancel,\n loading = false,\n icon,\n children,\n className,\n}: ConfirmDialogProps) {\n const cancelRef = useRef<HTMLButtonElement>(null);\n const confirmRef = useRef<HTMLButtonElement>(null);\n const danger = variant === \"danger\";\n\n return (\n <BlockModal\n open={open}\n onClose={onCancel}\n title={title}\n description={description}\n role=\"alertdialog\"\n size=\"sm\"\n icon={icon}\n hideCloseButton\n closeOnOverlayClick={!loading}\n closeOnEscape={!loading}\n initialFocusRef={danger ? cancelRef : confirmRef}\n className={className}\n data-variant={variant}\n footer={\n <>\n <BlockButton ref={cancelRef} variant=\"stone\" onClick={onCancel} disabled={loading}>\n {cancelText}\n </BlockButton>\n <BlockButton\n ref={confirmRef}\n variant={danger ? \"redstone\" : \"grass\"}\n onClick={onConfirm}\n loading={loading}\n >\n {confirmText}\n </BlockButton>\n </>\n }\n >\n {children}\n </BlockModal>\n );\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,cAAc,EAC5B,MACA,OACA,aACA,cAAc,WACd,aAAa,UACb,UAAU,WACV,WACA,UACA,UAAU,OACV,MACA,UACA,aACqB;CACrB,MAAM,YAAY,OAA0B,IAAI;CAChD,MAAM,aAAa,OAA0B,IAAI;CACjD,MAAM,SAAS,YAAY;CAE3B,OACE,oBAAC,YAAD;EACQ;EACN,SAAS;EACF;EACM;EACb,MAAK;EACL,MAAK;EACC;EACN,iBAAA;EACA,qBAAqB,CAAC;EACtB,eAAe,CAAC;EAChB,iBAAiB,SAAS,YAAY;EAC3B;EACX,gBAAc;EACd,QACE,qBAAAA,
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.js","names":["_Fragment"],"sources":["../../../../src/components/feedback/ConfirmDialog/ConfirmDialog.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport { BlockButton } from \"../../actions/BlockButton/BlockButton\";\nimport { BlockModal } from \"../Modal/BlockModal\";\nimport type { ConfirmDialogProps } from \"./ConfirmDialog.types\";\n\n/**\n * Confirmation built on `BlockModal` with `role=\"alertdialog\"`. For the\n * `danger` variant the safe action (Cancel) receives focus first.\n */\nexport function ConfirmDialog({\n open,\n title,\n description,\n confirmText = \"Confirm\",\n cancelText = \"Cancel\",\n variant = \"default\",\n onConfirm,\n onCancel,\n loading = false,\n icon,\n children,\n className,\n}: ConfirmDialogProps) {\n const cancelRef = useRef<HTMLButtonElement>(null);\n const confirmRef = useRef<HTMLButtonElement>(null);\n const danger = variant === \"danger\";\n\n return (\n <BlockModal\n open={open}\n onClose={onCancel}\n title={title}\n description={description}\n role=\"alertdialog\"\n size=\"sm\"\n icon={icon}\n hideCloseButton\n closeOnOverlayClick={!loading}\n closeOnEscape={!loading}\n initialFocusRef={danger ? cancelRef : confirmRef}\n className={className}\n data-variant={variant}\n footer={\n <>\n <BlockButton ref={cancelRef} variant=\"stone\" onClick={onCancel} disabled={loading}>\n {cancelText}\n </BlockButton>\n <BlockButton\n ref={confirmRef}\n variant={danger ? \"redstone\" : \"grass\"}\n onClick={onConfirm}\n loading={loading}\n >\n {confirmText}\n </BlockButton>\n </>\n }\n >\n {children}\n </BlockModal>\n );\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,cAAc,EAC5B,MACA,OACA,aACA,cAAc,WACd,aAAa,UACb,UAAU,WACV,WACA,UACA,UAAU,OACV,MACA,UACA,aACqB;CACrB,MAAM,YAAY,OAA0B,IAAI;CAChD,MAAM,aAAa,OAA0B,IAAI;CACjD,MAAM,SAAS,YAAY;CAE3B,OACE,oBAAC,YAAD;EACQ;EACN,SAAS;EACF;EACM;EACb,MAAK;EACL,MAAK;EACC;EACN,iBAAA;EACA,qBAAqB,CAAC;EACtB,eAAe,CAAC;EAChB,iBAAiB,SAAS,YAAY;EAC3B;EACX,gBAAc;EACd,QACE,qBAAAA,YAAA,EAAA,UAAA,CACE,oBAAC,aAAD;GAAa,KAAK;GAAW,SAAQ;GAAQ,SAAS;GAAU,UAAU;GACvE,UAAA;EACU,CAAA,GACb,oBAAC,aAAD;GACE,KAAK;GACL,SAAS,SAAS,aAAa;GAC/B,SAAS;GACA;GAER,UAAA;EACU,CAAA,CACb,EAAA,CAAA;EAGH;CACS,CAAA;AAEhB"}
|
|
@@ -4,7 +4,7 @@ import { useInventoryGrid, useSlotIndex } from "../InventoryGrid/context.js";
|
|
|
4
4
|
import InventorySlot_module_default from "./InventorySlot.module.js";
|
|
5
5
|
import { forwardRef, useCallback, useId, useLayoutEffect, useRef, useState } from "react";
|
|
6
6
|
import { LockIcon } from "@malilion/block-ui-icons";
|
|
7
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/inventory/InventorySlot/InventorySlot.tsx
|
|
9
9
|
/**
|
|
10
10
|
* A single inventory slot. Inside an `InventoryGrid` it becomes a roving-focus
|
|
@@ -61,7 +61,7 @@ var InventorySlot = forwardRef(function InventorySlot({ selected, disabled = fal
|
|
|
61
61
|
}
|
|
62
62
|
onKeyDown?.(event);
|
|
63
63
|
};
|
|
64
|
-
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
64
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
65
65
|
/* @__PURE__ */ jsx("span", {
|
|
66
66
|
className: InventorySlot_module_default.content,
|
|
67
67
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InventorySlot.js","names":["_Fragment","styles"],"sources":["../../../../src/components/inventory/InventorySlot/InventorySlot.tsx"],"sourcesContent":["import { LockIcon } from \"@malilion/block-ui-icons\";\nimport {\n forwardRef,\n useCallback,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n type Ref,\n} from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport { mergeRefs } from \"../../../utils/refs\";\nimport { useInventoryGrid, useSlotIndex } from \"../InventoryGrid/context\";\nimport styles from \"./InventorySlot.module.css\";\nimport type { InventorySlotProps } from \"./InventorySlot.types\";\n\ntype Placement = \"right\" | \"left\";\n\n/**\n * A single inventory slot. Inside an `InventoryGrid` it becomes a roving-focus\n * grid cell; standalone it is a toggle button (when `onClick` is set) or a\n * static well.\n */\nexport const InventorySlot = forwardRef<HTMLElement, InventorySlotProps>(function InventorySlot(\n {\n selected,\n disabled = false,\n locked = false,\n rarity,\n children,\n onClick,\n tooltip,\n label,\n size = \"md\",\n className,\n onKeyDown,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...rest\n },\n ref,\n) {\n const grid = useInventoryGrid();\n const index = useSlotIndex();\n const inGrid = grid !== null && index >= 0;\n const inactive = disabled || locked;\n const isSelected = selected ?? (inGrid ? grid.selectedIndex === index : false);\n const empty = children === undefined || children === null || children === false;\n /** Non-interactive wells cannot carry aria-label, so their label is rendered as hidden text. */\n const staticWell = !inGrid && !onClick;\n\n const tooltipId = useId();\n const [tooltipOpen, setTooltipOpen] = useState(false);\n const [placement, setPlacement] = useState<Placement>(\"right\");\n const tooltipRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n if (!tooltipOpen || !tooltipRef.current) return;\n const rect = tooltipRef.current.getBoundingClientRect();\n if (placement === \"right\" && rect.right > window.innerWidth - 8) setPlacement(\"left\");\n }, [tooltipOpen, placement]);\n\n const register = useCallback(\n (element: HTMLElement | null) => {\n if (inGrid) grid.registerCell(index, element);\n },\n [grid, inGrid, index],\n );\n\n const openTooltip = () => {\n if (tooltip) setTooltipOpen(true);\n };\n const closeTooltip = () => {\n setTooltipOpen(false);\n setPlacement(\"right\");\n };\n\n const handleClick = () => {\n if (inGrid) {\n grid.setFocusedIndex(index);\n if (!inactive) grid.select(index);\n }\n if (!inactive) onClick?.();\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.key === \"Escape\" && tooltipOpen) {\n event.stopPropagation();\n closeTooltip();\n }\n if (inGrid && (event.key === \"Enter\" || event.key === \" \")) {\n event.preventDefault();\n handleClick();\n }\n onKeyDown?.(event);\n };\n\n const content = (\n <>\n <span className={styles.content}>{children}</span>\n {empty && !label ? <span className=\"block-visually-hidden\">Empty slot</span> : null}\n {label && staticWell ? <span className=\"block-visually-hidden\">{label}</span> : null}\n {locked ? (\n <span className={styles.lock} aria-hidden=\"true\">\n <LockIcon size={16} />\n </span>\n ) : null}\n {tooltip ? (\n <div\n ref={tooltipRef}\n id={tooltipId}\n role=\"tooltip\"\n aria-hidden=\"true\"\n hidden={!tooltipOpen}\n className={styles.tooltip}\n data-placement={placement}\n >\n {tooltip}\n </div>\n ) : null}\n </>\n );\n\n const shared = {\n className: cx(styles.slot, !inGrid && styles[size], className),\n \"data-selected\": isSelected || undefined,\n \"data-disabled\": disabled || undefined,\n \"data-locked\": locked || undefined,\n \"data-rarity\": rarity,\n \"data-empty\": empty || undefined,\n \"aria-label\": staticWell ? undefined : label,\n \"aria-describedby\": tooltip ? tooltipId : undefined,\n onKeyDown: handleKeyDown,\n onMouseEnter: (event: MouseEvent<HTMLElement>) => {\n openTooltip();\n onMouseEnter?.(event);\n },\n onMouseLeave: (event: MouseEvent<HTMLElement>) => {\n closeTooltip();\n onMouseLeave?.(event);\n },\n onFocus: (event: FocusEvent<HTMLElement>) => {\n openTooltip();\n onFocus?.(event);\n },\n onBlur: (event: FocusEvent<HTMLElement>) => {\n closeTooltip();\n onBlur?.(event);\n },\n ...rest,\n };\n\n if (inGrid) {\n return (\n <div\n ref={mergeRefs(ref, register)}\n role=\"gridcell\"\n tabIndex={grid.focusedIndex === index ? 0 : -1}\n aria-selected={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n onKeyDown={handleKeyDown}\n >\n {content}\n </div>\n );\n }\n\n if (onClick) {\n return (\n <button\n ref={ref as Ref<HTMLButtonElement>}\n type=\"button\"\n aria-pressed={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n >\n {content}\n </button>\n );\n }\n\n return (\n // Non-interactive slots with tooltips must be focusable so keyboard users can trigger the tooltip\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n <div ref={ref as Ref<HTMLDivElement>} tabIndex={tooltip ? 0 : undefined} {...shared}>\n {content}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;AA0BA,IAAa,gBAAgB,WAA4C,SAAS,cAChF,EACE,UACA,WAAW,OACX,SAAS,OACT,QACA,UACA,SACA,SACA,OACA,OAAO,MACP,WACA,WACA,cACA,cACA,SACA,QACA,GAAG,QAEL,KACA;CACA,MAAM,OAAO,iBAAiB;CAC9B,MAAM,QAAQ,aAAa;CAC3B,MAAM,SAAS,SAAS,QAAQ,SAAS;CACzC,MAAM,WAAW,YAAY;CAC7B,MAAM,aAAa,aAAa,SAAS,KAAK,kBAAkB,QAAQ;CACxE,MAAM,QAAQ,aAAa,KAAA,KAAa,aAAa,QAAQ,aAAa;;CAE1E,MAAM,aAAa,CAAC,UAAU,CAAC;CAE/B,MAAM,YAAY,MAAM;CACxB,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAoB,OAAO;CAC7D,MAAM,aAAa,OAAuB,IAAI;CAE9C,sBAAsB;EACpB,IAAI,CAAC,eAAe,CAAC,WAAW,SAAS;EACzC,MAAM,OAAO,WAAW,QAAQ,sBAAsB;EACtD,IAAI,cAAc,WAAW,KAAK,QAAQ,OAAO,aAAa,GAAG,aAAa,MAAM;CACtF,GAAG,CAAC,aAAa,SAAS,CAAC;CAE3B,MAAM,WAAW,aACd,YAAgC;EAC/B,IAAI,QAAQ,KAAK,aAAa,OAAO,OAAO;CAC9C,GACA;EAAC;EAAM;EAAQ;CAAK,CACtB;CAEA,MAAM,oBAAoB;EACxB,IAAI,SAAS,eAAe,IAAI;CAClC;CACA,MAAM,qBAAqB;EACzB,eAAe,KAAK;EACpB,aAAa,OAAO;CACtB;CAEA,MAAM,oBAAoB;EACxB,IAAI,QAAQ;GACV,KAAK,gBAAgB,KAAK;GAC1B,IAAI,CAAC,UAAU,KAAK,OAAO,KAAK;EAClC;EACA,IAAI,CAAC,UAAU,UAAU;CAC3B;CAEA,MAAM,iBAAiB,UAAsC;EAC3D,IAAI,MAAM,QAAQ,YAAY,aAAa;GACzC,MAAM,gBAAgB;GACtB,aAAa;EACf;EACA,IAAI,WAAW,MAAM,QAAQ,WAAW,MAAM,QAAQ,MAAM;GAC1D,MAAM,eAAe;GACrB,YAAY;EACd;EACA,YAAY,KAAK;CACnB;CAEA,MAAM,UACJ,qBAAAA,
|
|
1
|
+
{"version":3,"file":"InventorySlot.js","names":["_Fragment","styles"],"sources":["../../../../src/components/inventory/InventorySlot/InventorySlot.tsx"],"sourcesContent":["import { LockIcon } from \"@malilion/block-ui-icons\";\nimport {\n forwardRef,\n useCallback,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n type Ref,\n} from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport { mergeRefs } from \"../../../utils/refs\";\nimport { useInventoryGrid, useSlotIndex } from \"../InventoryGrid/context\";\nimport styles from \"./InventorySlot.module.css\";\nimport type { InventorySlotProps } from \"./InventorySlot.types\";\n\ntype Placement = \"right\" | \"left\";\n\n/**\n * A single inventory slot. Inside an `InventoryGrid` it becomes a roving-focus\n * grid cell; standalone it is a toggle button (when `onClick` is set) or a\n * static well.\n */\nexport const InventorySlot = forwardRef<HTMLElement, InventorySlotProps>(function InventorySlot(\n {\n selected,\n disabled = false,\n locked = false,\n rarity,\n children,\n onClick,\n tooltip,\n label,\n size = \"md\",\n className,\n onKeyDown,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...rest\n },\n ref,\n) {\n const grid = useInventoryGrid();\n const index = useSlotIndex();\n const inGrid = grid !== null && index >= 0;\n const inactive = disabled || locked;\n const isSelected = selected ?? (inGrid ? grid.selectedIndex === index : false);\n const empty = children === undefined || children === null || children === false;\n /** Non-interactive wells cannot carry aria-label, so their label is rendered as hidden text. */\n const staticWell = !inGrid && !onClick;\n\n const tooltipId = useId();\n const [tooltipOpen, setTooltipOpen] = useState(false);\n const [placement, setPlacement] = useState<Placement>(\"right\");\n const tooltipRef = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n if (!tooltipOpen || !tooltipRef.current) return;\n const rect = tooltipRef.current.getBoundingClientRect();\n if (placement === \"right\" && rect.right > window.innerWidth - 8) setPlacement(\"left\");\n }, [tooltipOpen, placement]);\n\n const register = useCallback(\n (element: HTMLElement | null) => {\n if (inGrid) grid.registerCell(index, element);\n },\n [grid, inGrid, index],\n );\n\n const openTooltip = () => {\n if (tooltip) setTooltipOpen(true);\n };\n const closeTooltip = () => {\n setTooltipOpen(false);\n setPlacement(\"right\");\n };\n\n const handleClick = () => {\n if (inGrid) {\n grid.setFocusedIndex(index);\n if (!inactive) grid.select(index);\n }\n if (!inactive) onClick?.();\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.key === \"Escape\" && tooltipOpen) {\n event.stopPropagation();\n closeTooltip();\n }\n if (inGrid && (event.key === \"Enter\" || event.key === \" \")) {\n event.preventDefault();\n handleClick();\n }\n onKeyDown?.(event);\n };\n\n const content = (\n <>\n <span className={styles.content}>{children}</span>\n {empty && !label ? <span className=\"block-visually-hidden\">Empty slot</span> : null}\n {label && staticWell ? <span className=\"block-visually-hidden\">{label}</span> : null}\n {locked ? (\n <span className={styles.lock} aria-hidden=\"true\">\n <LockIcon size={16} />\n </span>\n ) : null}\n {tooltip ? (\n <div\n ref={tooltipRef}\n id={tooltipId}\n role=\"tooltip\"\n aria-hidden=\"true\"\n hidden={!tooltipOpen}\n className={styles.tooltip}\n data-placement={placement}\n >\n {tooltip}\n </div>\n ) : null}\n </>\n );\n\n const shared = {\n className: cx(styles.slot, !inGrid && styles[size], className),\n \"data-selected\": isSelected || undefined,\n \"data-disabled\": disabled || undefined,\n \"data-locked\": locked || undefined,\n \"data-rarity\": rarity,\n \"data-empty\": empty || undefined,\n \"aria-label\": staticWell ? undefined : label,\n \"aria-describedby\": tooltip ? tooltipId : undefined,\n onKeyDown: handleKeyDown,\n onMouseEnter: (event: MouseEvent<HTMLElement>) => {\n openTooltip();\n onMouseEnter?.(event);\n },\n onMouseLeave: (event: MouseEvent<HTMLElement>) => {\n closeTooltip();\n onMouseLeave?.(event);\n },\n onFocus: (event: FocusEvent<HTMLElement>) => {\n openTooltip();\n onFocus?.(event);\n },\n onBlur: (event: FocusEvent<HTMLElement>) => {\n closeTooltip();\n onBlur?.(event);\n },\n ...rest,\n };\n\n if (inGrid) {\n return (\n <div\n ref={mergeRefs(ref, register)}\n role=\"gridcell\"\n tabIndex={grid.focusedIndex === index ? 0 : -1}\n aria-selected={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n onKeyDown={handleKeyDown}\n >\n {content}\n </div>\n );\n }\n\n if (onClick) {\n return (\n <button\n ref={ref as Ref<HTMLButtonElement>}\n type=\"button\"\n aria-pressed={isSelected}\n aria-disabled={inactive || undefined}\n onClick={handleClick}\n {...shared}\n >\n {content}\n </button>\n );\n }\n\n return (\n // Non-interactive slots with tooltips must be focusable so keyboard users can trigger the tooltip\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n <div ref={ref as Ref<HTMLDivElement>} tabIndex={tooltip ? 0 : undefined} {...shared}>\n {content}\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BlockContainerProps } from "./BlockContainer.types";
|
|
2
|
+
/** Centres content at a readable max width with 16px side gutters. */
|
|
3
|
+
export declare const BlockContainer: import("react").ForwardRefExoticComponent<BlockContainerProps & import("react").RefAttributes<HTMLElement>>;
|
|
4
|
+
//# sourceMappingURL=BlockContainer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockContainer.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/BlockContainer/BlockContainer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,sEAAsE;AACtE,eAAO,MAAM,cAAc,6GAezB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cx } from "../../../utils/cx.js";
|
|
2
|
+
import BlockContainer_module_default from "./BlockContainer.module.js";
|
|
3
|
+
import { createElement, forwardRef } from "react";
|
|
4
|
+
//#region src/components/layout/BlockContainer/BlockContainer.tsx
|
|
5
|
+
/** Centres content at a readable max width with 16px side gutters. */
|
|
6
|
+
var BlockContainer = forwardRef(function BlockContainer({ size = "lg", flush = false, as = "div", className, children, ...rest }, ref) {
|
|
7
|
+
return createElement(as, {
|
|
8
|
+
ref,
|
|
9
|
+
"data-size": size,
|
|
10
|
+
"data-flush": flush || void 0,
|
|
11
|
+
className: cx(BlockContainer_module_default.container, className),
|
|
12
|
+
...rest
|
|
13
|
+
}, children);
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { BlockContainer };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=BlockContainer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockContainer.js","names":["styles"],"sources":["../../../../src/components/layout/BlockContainer/BlockContainer.tsx"],"sourcesContent":["import { createElement, forwardRef } from \"react\";\nimport { cx } from \"../../../utils/cx\";\nimport styles from \"./BlockContainer.module.css\";\nimport type { BlockContainerProps } from \"./BlockContainer.types\";\n\n/** Centres content at a readable max width with 16px side gutters. */\nexport const BlockContainer = forwardRef<HTMLElement, BlockContainerProps>(function BlockContainer(\n { size = \"lg\", flush = false, as = \"div\", className, children, ...rest },\n ref,\n) {\n return createElement(\n as,\n {\n ref,\n \"data-size\": size,\n \"data-flush\": flush || undefined,\n className: cx(styles.container, className),\n ...rest,\n },\n children,\n );\n});\n"],"mappings":";;;;;AAMA,IAAa,iBAAiB,WAA6C,SAAS,eAClF,EAAE,OAAO,MAAM,QAAQ,OAAO,KAAK,OAAO,WAAW,UAAU,GAAG,QAClE,KACA;CACA,OAAO,cACL,IACA;EACE;EACA,aAAa;EACb,cAAc,SAAS,KAAA;EACvB,WAAW,GAAGA,8BAAO,WAAW,SAAS;EACzC,GAAG;CACL,GACA,QACF;AACF,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/components/layout/BlockContainer/BlockContainer.module.css
|
|
2
|
+
var container = "block-container-UJuv-";
|
|
3
|
+
var BlockContainer_module_default = { container };
|
|
4
|
+
//#endregion
|
|
5
|
+
export { container, BlockContainer_module_default as default };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=BlockContainer.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockContainer.module.js","names":[],"sources":["../../../../src/components/layout/BlockContainer/BlockContainer.module.css"],"sourcesContent":[".container {\n box-sizing: border-box;\n width: 100%;\n max-width: 1024px;\n margin-inline: auto;\n padding-inline: var(--block-space-4);\n}\n\n.container[data-size=\"sm\"] {\n max-width: 640px;\n}\n\n.container[data-size=\"md\"] {\n max-width: 768px;\n}\n\n.container[data-size=\"xl\"] {\n max-width: 1280px;\n}\n\n.container[data-size=\"full\"] {\n max-width: none;\n}\n\n.container[data-flush] {\n padding-inline: 0;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export interface BlockContainerProps extends HTMLAttributes<HTMLElement> {
|
|
3
|
+
/** Max width: 640 / 768 / 1024 / 1280 px (the breakpoints) or `full`. @default "lg" */
|
|
4
|
+
size?: "sm" | "md" | "lg" | "xl" | "full";
|
|
5
|
+
/** Remove the 16px side gutters. */
|
|
6
|
+
flush?: boolean;
|
|
7
|
+
/** Render as a different element. @default "div" */
|
|
8
|
+
as?: "div" | "main" | "section" | "article" | "header" | "footer";
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=BlockContainer.types.d.ts.map
|