git.iko.soy/rowtype-yoga/purescript-yoga-heroui3
on main help sign in
40 files changed+201−116
since splitsnapshots
unifiedsplit
.github/workflows/deploy-stories.yml+54 .gitignore+2 bun.lock+4−2 package.json+1−1 spago.lock+7−3 spago.yaml+5 src/HeroUI/Autocomplete.purs+30−2 src/HeroUI/Raw.js+2 src/HeroUI/Raw.purs+1 src/HeroUI/Toast.purs+11−8 test/HeroUI/Accordion/Stories.purs+2−3 test/HeroUI/Alert/Stories.purs+2−3 test/HeroUI/Autocomplete/Stories.purs+13−7 test/HeroUI/Avatar/Stories.purs+1−2 test/HeroUI/Badge/Stories.purs+2−3 test/HeroUI/Breadcrumbs/Stories.purs+3−4 test/HeroUI/Button/Stories.purs+1−2 test/HeroUI/Card/Stories.purs+3−4 test/HeroUI/Checkbox/Stories.purs+1−2 test/HeroUI/Chip/Stories.purs+1−2 test/HeroUI/DatePicker/Stories.purs+1−2 test/HeroUI/Drawer/Stories.purs+4−5 test/HeroUI/Dropdown/Stories.purs+4−5 test/HeroUI/Form/Stories.purs+1−2 test/HeroUI/Kbd/Stories.purs+1−2 test/HeroUI/Link/Stories.purs+1−2 test/HeroUI/ListBox/Stories.purs+3−4 test/HeroUI/Menu/Stories.purs+3−4 test/HeroUI/Modal/Stories.purs+4−5 test/HeroUI/NumberField/Stories.purs+2−3 test/HeroUI/Pagination/Stories.purs+5−6 test/HeroUI/Popover/Stories.purs+2−2 test/HeroUI/Radio/Stories.purs+3−4 test/HeroUI/Select/Stories.purs+3−4 test/HeroUI/Switch/Stories.purs+1−2 test/HeroUI/Table/Stories.purs+4−5 test/HeroUI/Tabs/Stories.purs+4−5 test/HeroUI/Toast/Stories.purs+2−3 test/HeroUI/Tooltip/Stories.purs+2−3 yoga-stories-head.js+5
.github/workflows/deploy-stories.ymlnew file+54
@@ -0,0 +1,54 @@
1+name: Deploy Stories to GitHub Pages
2+
3+on:
4+ push:
5+ branches: [main]
6+ workflow_dispatch:
7+
8+permissions:
9+ contents: read
10+ pages: write
11+ id-token: write
12+
13+concurrency:
14+ group: pages
15+ cancel-in-progress: true
16+
17+jobs:
18+ build-and-deploy:
19+ runs-on: ubuntu-latest
20+ environment:
21+ name: github-pages
22+ url: ${{ steps.deployment.outputs.page_url }}
23+ steps:
24+ - uses: actions/checkout@v4
25+
26+ - uses: oven-sh/setup-bun@v2
27+ with:
28+ bun-version: latest
29+
30+ - uses: purescript-contrib/setup-purescript@main
31+ with:
32+ purescript: "0.15.15"
33+ spago: "0.93.42"
34+
35+ - run: npm install -g purs-backend-es@1.4.2
36+
37+ - name: Install JS dependencies
38+ run: |
39+ rm -f bun.lock
40+ npm install
41+ test -f node_modules/vite/dist/node/chunks/config.js || npm install vite@7.3.1
42+
43+ - run: spago build
44+
45+ - run: npx yoga-stories build
46+
47+ - uses: actions/configure-pages@v5
48+
49+ - uses: actions/upload-pages-artifact@v3
50+ with:
51+ path: dist
52+
53+ - uses: actions/deploy-pages@v4
54+ id: deployment
.gitignore+2
@@ -1,3 +1,5 @@
11node_modules/
22output/
3+output-es/
34.spago/
5+dist/
bun.lock+4−2
@@ -6,7 +6,7 @@
66 "dependencies": {
77 "@heroui/react": "^3.1.0",
88 "@heroui/styles": "^3.1.0",
9− "yoga-stories": "0.1.5",
9+ "yoga-stories": "0.1.13",
1010 },
1111 "devDependencies": {
1212 "@tailwindcss/vite": "^4.1.18",
@@ -120,6 +120,8 @@
120120
121121 "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
122122
123+ "@fontsource/jetbrains-mono": ["@fontsource/jetbrains-mono@5.2.8", "", {}, "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ=="],
124+
123125 "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.6", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw=="],
124126
125127 "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="],
@@ -538,7 +540,7 @@
538540
539541 "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
540542
541− "yoga-stories": ["yoga-stories@0.1.5", "", { "dependencies": { "@codemirror/language": "^6.12.1", "@codemirror/legacy-modes": "^6.5.2", "@uiw/codemirror-theme-tokyo-night": "^4.25.4", "@uiw/react-codemirror": "^4.25.4", "vite": "^7.3.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "bin": { "yoga-stories": "bin/cli.js" } }, "sha512-XeXNxbUVwcsmGRm3NZ/SBqSxmKl+vb06MI2fDUsB69dXKt2nVtOsd5qiLRQd73dOp1y9ub/LjuwAUsnNQdLHbA=="],
543+ "yoga-stories": ["yoga-stories@0.1.13", "", { "dependencies": { "@codemirror/language": "^6.12.1", "@codemirror/legacy-modes": "^6.5.2", "@fontsource/jetbrains-mono": "^5.2.8", "@uiw/codemirror-theme-tokyo-night": "^4.25.4", "@uiw/react-codemirror": "^4.25.4", "vite": "^7.3.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "bin": { "yoga-stories": "bin/cli.js" } }, "sha512-oa57HWyChSlvhloJhe1CMubw9nDWy44Jn6GYcWcruwtPzMTAEaoh2665wIptSKFdw5iErSfhWEUt03nHAZ+FrQ=="],
542544
543545 "@adobe/react-spectrum/@react-types/shared": ["@react-types/shared@3.35.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-iNWvuzEwANttpQpdlu8nPBtdHb0mcCMj1ZTH//iRB5E/14IAnyRlR25rxH7pNLyzHINsPGEKnWvpwDMCT6vziQ=="],
544546
package.json+1−1
@@ -5,7 +5,7 @@
55 "dependencies": {
66 "@heroui/react": "^3.1.0",
77 "@heroui/styles": "^3.1.0",
8− "yoga-stories": "0.1.5"
8+ "yoga-stories": "0.1.13"
99 },
1010 "peerDependencies": {
1111 "react": ">=19",
spago.lock+7−3
@@ -76,6 +76,10 @@
7676 "react-testing-library": {
7777 "git": "https://github.com/i-am-the-slime/purescript-react-testing-library.git",
7878 "ref": "fd679db77337485058907ba5c6cbac3b478de342"
79+ },
80+ "yoga-stories": {
81+ "git": "https://github.com/rowtype-yoga/purescript-yoga-stories.git",
82+ "ref": "v0.1.13"
7983 }
8084 }
8185 },
@@ -1532,9 +1536,9 @@
15321536 ]
15331537 },
15341538 "yoga-stories": {
1535− "type": "registry",
1536− "version": "0.1.0",
1537− "integrity": "sha256-8T+wUThPhXYH2MgSoe1/JjEmUnABk2A012wHliNLLcE=",
1539+ "type": "git",
1540+ "url": "https://github.com/rowtype-yoga/purescript-yoga-stories.git",
1541+ "rev": "8352da39a4d48dbb57416e1239692401b89a12fe",
15381542 "dependencies": [
15391543 "aff",
15401544 "arrays",
spago.yaml+5
@@ -3,6 +3,11 @@ workspace:
33 react-testing-library:
44 git: https://github.com/i-am-the-slime/purescript-react-testing-library.git
55 ref: fd679db77337485058907ba5c6cbac3b478de342
6+ # Pinned to the git ref because the registry package is still 0.1.0, which
7+ # predates the registry-aware story loader the 0.1.13 CLI build needs.
8+ yoga-stories:
9+ git: https://github.com/rowtype-yoga/purescript-yoga-stories.git
10+ ref: v0.1.13
611
712package:
813 name: yoga-heroui3
src/HeroUI/Autocomplete.purs+30−2
@@ -6,7 +6,8 @@ import Effect (Effect)
66import Effect.Uncurried (EffectFn1)
77import React.Basic (JSX)
88import Yoga.React.DOM.Internal (class IsJSX)
9−import Data.Function.Uncurried (runFn4)
9+import Data.Function.Uncurried (Fn2, mkFn2, runFn4)
10+import Data.String (Pattern(..), contains, toLower)
1011import HeroUI.Types (FieldVariant, fieldVariantToString)
1112import HeroUI.Internal (class CoerceHeroProps, createElementTransformImpl)
1213import HeroUI.Raw as Raw
@@ -51,10 +52,13 @@ autocompleteTrigger
5152 -> JSX
5253autocompleteTrigger props kids = runFn4 createElementTransformImpl {} Raw.autocompleteTrigger props kids
5354
55+-- | Shows the selected option's text, or `placeholder` when nothing is selected.
56+type AutocompleteValueProps r = (placeholder :: String, className :: String | r)
57+
5458autocompleteValue
5559 :: forall givenProps nonDataProps kids
5660 . IsJSX kids
57− => CoerceHeroProps { | givenProps } { | nonDataProps } { | SectionProps () }
61+ => CoerceHeroProps { | givenProps } { | nonDataProps } { | AutocompleteValueProps () }
5862 => { | givenProps }
5963 -> kids
6064 -> JSX
@@ -78,6 +82,30 @@ autocompletePopover
7882 -> JSX
7983autocompletePopover props kids = runFn4 createElementTransformImpl {} Raw.autocompletePopover props kids
8084
85+-- | Wraps the search `input` + the options collection inside the popover;
86+-- | this is what makes the autocomplete typeable. Pass `filter` to narrow the
87+-- | options as the user types (see `containsFilter` for the common case).
88+type AutocompleteFilterProps r =
89+ ( filter :: Fn2 String String Boolean
90+ , className :: String
91+ | r
92+ )
93+
94+autocompleteFilter
95+ :: forall givenProps nonDataProps kids
96+ . IsJSX kids
97+ => CoerceHeroProps { | givenProps } { | nonDataProps } { | AutocompleteFilterProps () }
98+ => { | givenProps }
99+ -> kids
100+ -> JSX
101+autocompleteFilter props kids = runFn4 createElementTransformImpl {} Raw.autocompleteFilter props kids
102+
103+-- | Case-insensitive substring match — the usual `filter` for `autocompleteFilter`.
104+-- | react-aria calls it as `filter optionTextValue typedInput`; keep an option
105+-- | when what the user typed appears anywhere in its `textValue`.
106+containsFilter :: Fn2 String String Boolean
107+containsFilter = mkFn2 \optionText typed -> contains (Pattern (toLower typed)) (toLower optionText)
108+
81109autocompleteClearButton
82110 :: forall givenProps nonDataProps kids
83111 . IsJSX kids
src/HeroUI/Raw.js+2
@@ -87,6 +87,7 @@ import {
8787 AutocompleteIndicator,
8888 AutocompletePopover,
8989 AutocompleteClearButton,
90+ AutocompleteFilter,
9091 DateFieldRoot,
9192 TimeFieldRoot,
9293 DatePickerRoot,
@@ -338,6 +339,7 @@ export const autocompleteValue = AutocompleteValue;
338339export const autocompleteIndicator = AutocompleteIndicator;
339340export const autocompletePopover = AutocompletePopover;
340341export const autocompleteClearButton = AutocompleteClearButton;
342+export const autocompleteFilter = AutocompleteFilter;
341343
342344// DateField / TimeField
343345export const dateField = DateFieldRoot;
src/HeroUI/Raw.purs+1
@@ -137,6 +137,7 @@ foreign import autocompleteValue :: forall r. ReactComponent { | r }
137137foreign import autocompleteIndicator :: forall r. ReactComponent { | r }
138138foreign import autocompletePopover :: forall r. ReactComponent { | r }
139139foreign import autocompleteClearButton :: forall r. ReactComponent { | r }
140+foreign import autocompleteFilter :: forall r. ReactComponent { | r }
140141
141142-- DateField / TimeField
142143foreign import dateField :: forall r. ReactComponent { | r }
src/HeroUI/Toast.purs+11−8
@@ -9,6 +9,7 @@ import Yoga.React.DOM.Internal (class IsJSX)
99import Data.Function.Uncurried (runFn4)
1010import HeroUI.Internal (class CoerceHeroProps, createElementTransformImpl)
1111import HeroUI.Raw as Raw
12+import Unsafe.Coerce (unsafeCoerce)
1213
1314-- Render the provider once near the root of the app; fire toasts imperatively.
1415type ToastProviderProps r =
@@ -64,17 +65,19 @@ foreign import closeAllToastsImpl :: Effect Unit
6465foreign import pauseAllToastsImpl :: Effect Unit
6566foreign import resumeAllToastsImpl :: Effect Unit
6667
67−toastSuccess :: JSX -> Effect String
68−toastSuccess = runEffectFn1 toastSuccessImpl
68+-- | The message accepts anything renderable (`IsJSX`): a plain `String`, a
69+-- | `JSX` element, or an `Array JSX`.
70+toastSuccess :: forall msg. IsJSX msg => msg -> Effect String
71+toastSuccess msg = runEffectFn1 toastSuccessImpl (unsafeCoerce msg)
6972
70−toastDanger :: JSX -> Effect String
71−toastDanger = runEffectFn1 toastDangerImpl
73+toastDanger :: forall msg. IsJSX msg => msg -> Effect String
74+toastDanger msg = runEffectFn1 toastDangerImpl (unsafeCoerce msg)
7275
73−toastInfo :: JSX -> Effect String
74−toastInfo = runEffectFn1 toastInfoImpl
76+toastInfo :: forall msg. IsJSX msg => msg -> Effect String
77+toastInfo msg = runEffectFn1 toastInfoImpl (unsafeCoerce msg)
7578
76−toastWarning :: JSX -> Effect String
77−toastWarning = runEffectFn1 toastWarningImpl
79+toastWarning :: forall msg. IsJSX msg => msg -> Effect String
80+toastWarning msg = runEffectFn1 toastWarningImpl (unsafeCoerce msg)
7881
7982closeToast :: String -> Effect Unit
8083closeToast = runEffectFn1 closeToastImpl
test/HeroUI/Accordion/Stories.purs+2−3
@@ -6,14 +6,13 @@ import React.Basic (JSX)
66import HeroUI.Accordion as Accordion
77import Yoga.React (component)
88import Yoga.React.DOM.HTML (div)
9−import Yoga.React.DOM.Internal (text)
109import YogaStories.Story (story)
1110
1211item :: String -> String -> String -> JSX
1312item key heading body =
1413 Accordion.accordionItem { id: key }
15− [ Accordion.accordionHeading {} [ Accordion.accordionTrigger {} (text heading) ]
16− , Accordion.accordionPanel {} [ Accordion.accordionBody {} (text body) ]
14+ [ Accordion.accordionHeading {} [ Accordion.accordionTrigger {} heading ]
15+ , Accordion.accordionPanel {} [ Accordion.accordionBody {} body ]
1716 ]
1817
1918mkAccordion :: {} -> JSX
test/HeroUI/Alert/Stories.purs+2−3
@@ -8,7 +8,6 @@ import HeroUI.Alert as Alert
88import HeroUI.Types as T
99import Yoga.React (component)
1010import Yoga.React.DOM.HTML (div)
11−import Yoga.React.DOM.Internal (text)
1211import YogaStories.Controls (enum)
1312import YogaStories.Story (story)
1413
@@ -30,8 +29,8 @@ mkAlert = component "AlertStory" \props -> React.do
3029 [ Alert.alert { status: toStatus props.status }
3130 [ Alert.alertIndicator {} ([] :: Array JSX)
3231 , Alert.alertContent {}
33− [ Alert.alertTitle {} (text props.title)
34− , Alert.alertDescription {} (text props.body)
32+ [ Alert.alertTitle {} props.title
33+ , Alert.alertDescription {} props.body
3534 ]
3635 ]
3736 ]
test/HeroUI/Autocomplete/Stories.purs+13−7
@@ -4,25 +4,31 @@ import Prelude hiding (div)
44
55import React.Basic (JSX)
66import HeroUI.Autocomplete as Autocomplete
7+import HeroUI.Input as Input
78import HeroUI.ListBox as ListBox
89import Yoga.React (component)
910import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1111import YogaStories.Story (story)
1212
1313mkAutocomplete :: { placeholder :: String } -> JSX
1414mkAutocomplete = component "AutocompleteStory" \props -> React.do
1515 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-64" }
16− [ Autocomplete.autocomplete { placeholder: props.placeholder, defaultSelectedKey: "apple", "aria-label": "Fruit" }
16+ [ Autocomplete.autocomplete { placeholder: props.placeholder, "aria-label": "Fruit" }
1717 [ Autocomplete.autocompleteTrigger {}
18− [ Autocomplete.autocompleteValue {} ([] :: Array JSX)
18+ [ Autocomplete.autocompleteValue { placeholder: props.placeholder } ([] :: Array JSX)
1919 , Autocomplete.autocompleteIndicator {} ([] :: Array JSX)
2020 ]
2121 , Autocomplete.autocompletePopover {}
22− [ ListBox.listBox {}
23− [ ListBox.listBoxItem { id: "apple" } (text "Apple")
24− , ListBox.listBoxItem { id: "banana" } (text "Banana")
25− , ListBox.listBoxItem { id: "cherry" } (text "Cherry")
22+ [ Autocomplete.autocompleteFilter { filter: Autocomplete.containsFilter }
23+ [ Input.textField { "aria-label": "Search fruit" }
24+ [ Input.input { placeholder: "Type to filter…" } ([] :: Array JSX) ]
25+ , ListBox.listBox {}
26+ [ ListBox.listBoxItem { id: "apple", textValue: "Apple" } "Apple"
27+ , ListBox.listBoxItem { id: "banana", textValue: "Banana" } "Banana"
28+ , ListBox.listBoxItem { id: "cherry", textValue: "Cherry" } "Cherry"
29+ , ListBox.listBoxItem { id: "date", textValue: "Date" } "Date"
30+ , ListBox.listBoxItem { id: "elderberry", textValue: "Elderberry" } "Elderberry"
31+ ]
2632 ]
2733 ]
2834 ]
test/HeroUI/Avatar/Stories.purs+1−2
@@ -7,14 +7,13 @@ import HeroUI.Avatar as Avatar
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkAvatar :: { initials :: String } -> JSX
1413mkAvatar = component "AvatarStory" \props -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
1615 [ Avatar.avatar { color: T.Accent, size: T.Md, variant: Avatar.Soft }
17− [ Avatar.avatarFallback { color: T.Accent } (text props.initials) ]
16+ [ Avatar.avatarFallback { color: T.Accent } props.initials ]
1817 ]
1918
2019basic :: JSX
test/HeroUI/Badge/Stories.purs+2−3
@@ -8,15 +8,14 @@ import HeroUI.Badge as Badge
88import HeroUI.Types as T
99import Yoga.React (component)
1010import Yoga.React.DOM.HTML (div)
11−import Yoga.React.DOM.Internal (text)
1211import YogaStories.Story (story)
1312
1413mkBadge :: { count :: String } -> JSX
1514mkBadge = component "BadgeStory" \props -> React.do
1615 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
1716 [ Badge.badge { color: T.Danger, variant: Badge.Primary, placement: Badge.TopRight, size: T.Md }
18− [ Badge.badgeLabel {} (text props.count)
19− , Badge.badgeAnchor {} [ Avatar.avatar { color: T.Accent } [ Avatar.avatarFallback {} (text "ME") ] ]
17+ [ Badge.badgeLabel {} props.count
18+ , Badge.badgeAnchor {} [ Avatar.avatar { color: T.Accent } [ Avatar.avatarFallback {} "ME" ] ]
2019 ]
2120 ]
2221
test/HeroUI/Breadcrumbs/Stories.purs+3−4
@@ -6,16 +6,15 @@ import React.Basic (JSX)
66import HeroUI.Breadcrumbs as Breadcrumbs
77import Yoga.React (component)
88import Yoga.React.DOM.HTML (div)
9−import Yoga.React.DOM.Internal (text)
109import YogaStories.Story (story)
1110
1211mkBreadcrumbs :: {} -> JSX
1312mkBreadcrumbs = component "BreadcrumbsStory" \(_ :: {}) -> React.do
1413 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1514 [ Breadcrumbs.breadcrumbs {}
16− [ Breadcrumbs.breadcrumbsItem { href: "#" } (text "Home")
17− , Breadcrumbs.breadcrumbsItem { href: "#" } (text "Library")
18− , Breadcrumbs.breadcrumbsItem {} (text "Current")
15+ [ Breadcrumbs.breadcrumbsItem { href: "#" } "Home"
16+ , Breadcrumbs.breadcrumbsItem { href: "#" } "Library"
17+ , Breadcrumbs.breadcrumbsItem {} "Current"
1918 ]
2019 ]
2120
test/HeroUI/Button/Stories.purs+1−2
@@ -8,7 +8,6 @@ import HeroUI.Button as Btn
88import HeroUI.Types as T
99import Yoga.React (component)
1010import Yoga.React.DOM.HTML (div)
11−import Yoga.React.DOM.Internal (text)
1211import YogaStories.Controls (enum)
1312import YogaStories.Story (story)
1413
@@ -45,7 +44,7 @@ mkButton = component "ButtonStory" \props -> React.do
4544 , size: toSize props.size
4645 , isDisabled: props.isDisabled
4746 }
48− (text props.label)
47+ props.label
4948 ]
5049
5150primary :: JSX
test/HeroUI/Card/Stories.purs+3−4
@@ -7,7 +7,6 @@ import React.Basic (JSX)
77import HeroUI.Card as Card
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Controls (enum)
1211import YogaStories.Story (story)
1312
@@ -26,9 +25,9 @@ mkCard :: { title :: String, body :: String, variant :: Variant } -> JSX
2625mkCard = component "CardStory" \props -> React.do
2726 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
2827 [ Card.card { variant: toVariant props.variant, className: "max-w-sm" }
29− [ Card.cardHeader {} [ Card.cardTitle {} (text props.title) ]
30− , Card.cardContent {} (text props.body)
31− , Card.cardFooter {} (text "Footer")
28+ [ Card.cardHeader {} [ Card.cardTitle {} props.title ]
29+ , Card.cardContent {} props.body
30+ , Card.cardFooter {} "Footer"
3231 ]
3332 ]
3433
test/HeroUI/Checkbox/Stories.purs+1−2
@@ -7,13 +7,12 @@ import HeroUI.Checkbox as Checkbox
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkCheckbox :: { label :: String, defaultSelected :: Boolean } -> JSX
1413mkCheckbox = component "CheckboxStory" \props -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
16− [ Checkbox.checkbox { defaultSelected: props.defaultSelected, variant: T.Primary } (text props.label) ]
15+ [ Checkbox.checkbox { defaultSelected: props.defaultSelected, variant: T.Primary } props.label ]
1716
1817basic :: JSX
1918basic = story "basic" mkCheckbox { label: "Accept terms", defaultSelected: false }
test/HeroUI/Chip/Stories.purs+1−2
@@ -8,7 +8,6 @@ import HeroUI.Chip as Chip
88import HeroUI.Types as T
99import Yoga.React (component)
1010import Yoga.React.DOM.HTML (div)
11−import Yoga.React.DOM.Internal (text)
1211import YogaStories.Controls (enum)
1312import YogaStories.Story (story)
1413
@@ -26,7 +25,7 @@ toVariant = case _ of
2625mkChip :: { label :: String, variant :: Variant } -> JSX
2726mkChip = component "ChipStory" \props -> React.do
2827 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
29− [ Chip.chip { color: T.Accent, size: T.Md, variant: toVariant props.variant } (text props.label) ]
28+ [ Chip.chip { color: T.Accent, size: T.Md, variant: toVariant props.variant } props.label ]
3029
3130basic :: JSX
3231basic = story "basic" mkChip { label: "new", variant: enum Soft }
test/HeroUI/DatePicker/Stories.purs+1−2
@@ -6,14 +6,13 @@ import React.Basic (JSX)
66import HeroUI.DatePicker as DatePicker
77import Yoga.React (component)
88import Yoga.React.DOM.HTML (div)
9−import Yoga.React.DOM.Internal (text)
109import YogaStories.Story (story)
1110
1211mkDatePicker :: {} -> JSX
1312mkDatePicker = component "DatePickerStory" \(_ :: {}) -> React.do
1413 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-72" }
1514 [ DatePicker.datePicker {}
16− [ DatePicker.datePickerTrigger {} (text "Pick a date")
15+ [ DatePicker.datePickerTrigger {} "Pick a date"
1716 , DatePicker.datePickerPopover {} ([] :: Array JSX)
1817 ]
1918 ]
test/HeroUI/Drawer/Stories.purs+4−5
@@ -7,20 +7,19 @@ import HeroUI.Button as Btn
77import HeroUI.Drawer as Drawer
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkDrawer :: { title :: String } -> JSX
1413mkDrawer = component "DrawerStory" \props -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1615 [ Drawer.drawer {}
17− [ Btn.button { variant: Btn.Primary } (text "Open drawer")
16+ [ Btn.button { variant: Btn.Primary } "Open drawer"
1817 , Drawer.drawerBackdrop { variant: Drawer.Blur }
1918 [ Drawer.drawerContent { placement: Drawer.Right }
2019 [ Drawer.drawerDialog {}
21− [ Drawer.drawerHeader {} [ Drawer.drawerHeading {} (text props.title) ]
22− , Drawer.drawerBody {} (text "Drawer body content.")
23− , Drawer.drawerFooter {} [ Drawer.drawerCloseTrigger {} (text "Close") ]
20+ [ Drawer.drawerHeader {} [ Drawer.drawerHeading {} props.title ]
21+ , Drawer.drawerBody {} "Drawer body content."
22+ , Drawer.drawerFooter {} [ Drawer.drawerCloseTrigger {} "Close" ]
2423 ]
2524 ]
2625 ]
test/HeroUI/Dropdown/Stories.purs+4−5
@@ -8,19 +8,18 @@ import HeroUI.Dropdown as Dropdown
88import HeroUI.Types as T
99import Yoga.React (component)
1010import Yoga.React.DOM.HTML (div)
11−import Yoga.React.DOM.Internal (text)
1211import YogaStories.Story (story)
1312
1413mkDropdown :: {} -> JSX
1514mkDropdown = component "DropdownStory" \(_ :: {}) -> React.do
1615 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1716 [ Dropdown.dropdown {}
18− [ Dropdown.dropdownTrigger {} [ Btn.button { variant: Btn.Primary } (text "Actions") ]
17+ [ Dropdown.dropdownTrigger {} [ Btn.button { variant: Btn.Primary } "Actions" ]
1918 , Dropdown.dropdownPopover {}
2019 [ Dropdown.dropdownMenu { selectionMode: T.SelectionNone }
21− [ Dropdown.dropdownItem { id: "new" } (text "New file")
22− , Dropdown.dropdownItem { id: "copy" } (text "Copy link")
23− , Dropdown.dropdownItem { id: "delete" } (text "Delete")
20+ [ Dropdown.dropdownItem { id: "new" } "New file"
21+ , Dropdown.dropdownItem { id: "copy" } "Copy link"
22+ , Dropdown.dropdownItem { id: "delete" } "Delete"
2423 ]
2524 ]
2625 ]
test/HeroUI/Form/Stories.purs+1−2
@@ -8,7 +8,6 @@ import HeroUI.Form as Form
88import HeroUI.Input as Input
99import Yoga.React (component)
1010import Yoga.React.DOM.HTML (div)
11−import Yoga.React.DOM.Internal (text)
1211import YogaStories.Story (story)
1312
1413mkForm :: {} -> JSX
@@ -17,7 +16,7 @@ mkForm = component "FormStory" \(_ :: {}) -> React.do
1716 [ Form.form { validationBehavior: Form.Native, className: "flex flex-col gap-3" }
1817 [ Input.textField { name: "email" }
1918 [ Input.input { placeholder: "you@example.com", type: Input.InputTypeEmail } ([] :: Array JSX) ]
20− , Btn.button { type: Btn.ButtonTypeSubmit, variant: Btn.Primary } (text "Submit")
19+ , Btn.button { type: Btn.ButtonTypeSubmit, variant: Btn.Primary } "Submit"
2120 ]
2221 ]
2322
test/HeroUI/Kbd/Stories.purs+1−2
@@ -6,7 +6,6 @@ import React.Basic (JSX)
66import HeroUI.Kbd as Kbd
77import Yoga.React (component)
88import Yoga.React.DOM.HTML (div)
9−import Yoga.React.DOM.Internal (text)
109import YogaStories.Story (story)
1110
1211mkKbd :: { label :: String } -> JSX
@@ -14,7 +13,7 @@ mkKbd = component "KbdStory" \props -> React.do
1413 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-2" }
1514 [ Kbd.kbd { variant: Kbd.Default }
1615 [ Kbd.kbdAbbr { keyValue: Kbd.Command } ([] :: Array JSX)
17− , Kbd.kbdContent {} (text props.label)
16+ , Kbd.kbdContent {} props.label
1817 ]
1918 ]
2019
test/HeroUI/Link/Stories.purs+1−2
@@ -6,13 +6,12 @@ import React.Basic (JSX)
66import HeroUI.Link as Link
77import Yoga.React (component)
88import Yoga.React.DOM.HTML (div)
9−import Yoga.React.DOM.Internal (text)
109import YogaStories.Story (story)
1110
1211mkLink :: { label :: String, href :: String } -> JSX
1312mkLink = component "LinkStory" \props -> React.do
1413 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
15− [ Link.link { href: props.href, target: "_blank" } (text props.label) ]
14+ [ Link.link { href: props.href, target: "_blank" } props.label ]
1615
1716basic :: JSX
1817basic = story "basic" mkLink { label: "Visit HeroUI", href: "https://heroui.com" }
test/HeroUI/ListBox/Stories.purs+3−4
@@ -7,16 +7,15 @@ import HeroUI.ListBox as ListBox
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkListBox :: {} -> JSX
1413mkListBox = component "ListBoxStory" \(_ :: {}) -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-56" }
1615 [ ListBox.listBox { selectionMode: T.Multiple, defaultSelectedKeys: [ "a" ] }
17− [ ListBox.listBoxItem { id: "a" } (text "Apple")
18− , ListBox.listBoxItem { id: "b" } (text "Banana")
19− , ListBox.listBoxItem { id: "c" } (text "Cherry")
16+ [ ListBox.listBoxItem { id: "a" } "Apple"
17+ , ListBox.listBoxItem { id: "b" } "Banana"
18+ , ListBox.listBoxItem { id: "c" } "Cherry"
2019 ]
2120 ]
2221
test/HeroUI/Menu/Stories.purs+3−4
@@ -7,16 +7,15 @@ import HeroUI.Menu as Menu
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkMenu :: {} -> JSX
1413mkMenu = component "MenuStory" \(_ :: {}) -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-56" }
1615 [ Menu.menu { selectionMode: T.Single, defaultSelectedKeys: [ "profile" ] }
17− [ Menu.menuItem { id: "profile" } (text "Profile")
18− , Menu.menuItem { id: "settings" } (text "Settings")
19− , Menu.menuItem { id: "logout" } (text "Log out")
16+ [ Menu.menuItem { id: "profile" } "Profile"
17+ , Menu.menuItem { id: "settings" } "Settings"
18+ , Menu.menuItem { id: "logout" } "Log out"
2019 ]
2120 ]
2221
test/HeroUI/Modal/Stories.purs+4−5
@@ -7,20 +7,19 @@ import HeroUI.Button as Btn
77import HeroUI.Modal as Modal
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkModal :: { title :: String } -> JSX
1413mkModal = component "ModalStory" \props -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1615 [ Modal.modal {}
17− [ Btn.button { variant: Btn.Primary } (text "Open modal")
16+ [ Btn.button { variant: Btn.Primary } "Open modal"
1817 , Modal.modalBackdrop { variant: Modal.Blur }
1918 [ Modal.modalContainer { placement: Modal.PlacementCenter, size: Modal.Md }
2019 [ Modal.modalDialog {}
21− [ Modal.modalHeader {} [ Modal.modalHeading {} (text props.title) ]
22− , Modal.modalBody {} (text "Modal body content.")
23− , Modal.modalFooter {} [ Modal.modalCloseTrigger {} (text "Close") ]
20+ [ Modal.modalHeader {} [ Modal.modalHeading {} props.title ]
21+ , Modal.modalBody {} "Modal body content."
22+ , Modal.modalFooter {} [ Modal.modalCloseTrigger {} "Close" ]
2423 ]
2524 ]
2625 ]
test/HeroUI/NumberField/Stories.purs+2−3
@@ -6,7 +6,6 @@ import React.Basic (JSX)
66import HeroUI.NumberField as NumberField
77import Yoga.React (component)
88import Yoga.React.DOM.HTML (div)
9−import Yoga.React.DOM.Internal (text)
109import YogaStories.Story (story)
1110
1211mkNumberField :: { defaultValue :: Number } -> JSX
@@ -14,9 +13,9 @@ mkNumberField = component "NumberFieldStory" \props -> React.do
1413 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-60" }
1514 [ NumberField.numberField { defaultValue: props.defaultValue, minValue: 0.0, maxValue: 100.0, step: 1.0 }
1615 [ NumberField.numberFieldGroup {}
17− [ NumberField.numberFieldDecrementButton {} (text "−")
16+ [ NumberField.numberFieldDecrementButton {} "−"
1817 , NumberField.numberFieldInput {} ([] :: Array JSX)
19− , NumberField.numberFieldIncrementButton {} (text "+")
18+ , NumberField.numberFieldIncrementButton {} "+"
2019 ]
2120 ]
2221 ]
test/HeroUI/Pagination/Stories.purs+5−6
@@ -7,7 +7,6 @@ import HeroUI.Pagination as Pagination
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkPagination :: {} -> JSX
@@ -15,11 +14,11 @@ mkPagination = component "PaginationStory" \(_ :: {}) -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1615 [ Pagination.pagination { size: T.Md }
1716 [ Pagination.paginationContent {}
18− [ Pagination.paginationPrevious {} (text "‹")
19− , Pagination.paginationItem { isActive: true } (text "1")
20− , Pagination.paginationItem { isActive: false } (text "2")
21− , Pagination.paginationItem { isActive: false } (text "3")
22− , Pagination.paginationNext {} (text "›")
17+ [ Pagination.paginationPrevious {} "‹"
18+ , Pagination.paginationItem { isActive: true } "1"
19+ , Pagination.paginationItem { isActive: false } "2"
20+ , Pagination.paginationItem { isActive: false } "3"
21+ , Pagination.paginationNext {} "›"
2322 ]
2423 ]
2524 ]
test/HeroUI/Popover/Stories.purs+2−2
@@ -14,9 +14,9 @@ mkPopover :: {} -> JSX
1414mkPopover = component "PopoverStory" \(_ :: {}) -> React.do
1515 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1616 [ Popover.popover {}
17− [ Popover.popoverTrigger {} [ Btn.button { variant: Btn.Primary } (text "Open popover") ]
17+ [ Popover.popoverTrigger {} [ Btn.button { variant: Btn.Primary } "Open popover" ]
1818 , Popover.popoverContent { placement: "bottom", showArrow: true }
19− [ Popover.popoverHeading {} (text "Heading")
19+ [ Popover.popoverHeading {} "Heading"
2020 , text "Popover content."
2121 ]
2222 ]
test/HeroUI/Radio/Stories.purs+3−4
@@ -7,16 +7,15 @@ import HeroUI.Radio as Radio
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkRadio :: { defaultValue :: String } -> JSX
1413mkRadio = component "RadioStory" \props -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1615 [ Radio.radioGroup { defaultValue: props.defaultValue, orientation: T.Vertical, variant: T.Primary }
17− [ Radio.radio { value: "a" } (text "Option A")
18− , Radio.radio { value: "b" } (text "Option B")
19− , Radio.radio { value: "c" } (text "Option C")
16+ [ Radio.radio { value: "a" } "Option A"
17+ , Radio.radio { value: "b" } "Option B"
18+ , Radio.radio { value: "c" } "Option C"
2019 ]
2120 ]
2221
test/HeroUI/Select/Stories.purs+3−4
@@ -7,7 +7,6 @@ import HeroUI.ListBox as ListBox
77import HeroUI.Select as Select
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkSelect :: { placeholder :: String } -> JSX
@@ -17,9 +16,9 @@ mkSelect = component "SelectStory" \props -> React.do
1716 [ Select.selectTrigger {} [ Select.selectValue { placeholder: props.placeholder } ([] :: Array JSX) ]
1817 , Select.selectPopover {}
1918 [ ListBox.listBox {}
20− [ ListBox.listBoxItem { id: "apple" } (text "Apple")
21− , ListBox.listBoxItem { id: "banana" } (text "Banana")
22− , ListBox.listBoxItem { id: "cherry" } (text "Cherry")
19+ [ ListBox.listBoxItem { id: "apple" } "Apple"
20+ , ListBox.listBoxItem { id: "banana" } "Banana"
21+ , ListBox.listBoxItem { id: "cherry" } "Cherry"
2322 ]
2423 ]
2524 ]
test/HeroUI/Switch/Stories.purs+1−2
@@ -7,13 +7,12 @@ import HeroUI.Switch as Switch
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkSwitch :: { label :: String, defaultSelected :: Boolean } -> JSX
1413mkSwitch = component "SwitchStory" \props -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
16− [ Switch.switch_ { defaultSelected: props.defaultSelected, size: T.Md } (text props.label) ]
15+ [ Switch.switch_ { defaultSelected: props.defaultSelected, size: T.Md } props.label ]
1716
1817basic :: JSX
1918basic = story "basic" mkSwitch { label: "Wi-Fi", defaultSelected: true }
test/HeroUI/Table/Stories.purs+4−5
@@ -7,14 +7,13 @@ import HeroUI.Table as Table
77import HeroUI.Types as T
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312row :: String -> String -> String -> JSX
1413row key name role =
1514 Table.tableRow { id: key }
16− [ Table.tableCell {} (text name)
17− , Table.tableCell {} (text role)
15+ [ Table.tableCell {} name
16+ , Table.tableCell {} role
1817 ]
1918
2019mkTable :: {} -> JSX
@@ -23,8 +22,8 @@ mkTable = component "TableStory" \(_ :: {}) -> React.do
2322 [ Table.table {}
2423 [ Table.tableContent { selectionMode: T.Multiple, "aria-label": "People" }
2524 [ Table.tableHeader {}
26− [ Table.tableColumn { id: "name", isRowHeader: true } (text "Name")
27− , Table.tableColumn { id: "role" } (text "Role")
25+ [ Table.tableColumn { id: "name", isRowHeader: true } "Name"
26+ , Table.tableColumn { id: "role" } "Role"
2827 ]
2928 , Table.tableBody {}
3029 [ row "1" "Alice" "Admin"
test/HeroUI/Tabs/Stories.purs+4−5
@@ -7,7 +7,6 @@ import React.Basic (JSX)
77import HeroUI.Tabs as Tabs
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Controls (enum)
1211import YogaStories.Story (story)
1312
@@ -25,11 +24,11 @@ mkTabs = component "TabsStory" \props -> React.do
2524 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-96" }
2625 [ Tabs.tabs { variant: toVariant props.variant, defaultSelectedKey: "photos" }
2726 [ Tabs.tabList {}
28− [ Tabs.tab { id: "photos" } (text "Photos")
29− , Tabs.tab { id: "music" } (text "Music")
27+ [ Tabs.tab { id: "photos" } "Photos"
28+ , Tabs.tab { id: "music" } "Music"
3029 ]
31− , Tabs.tabPanel { id: "photos" } (text "Photos panel")
32− , Tabs.tabPanel { id: "music" } (text "Music panel")
30+ , Tabs.tabPanel { id: "photos" } "Photos panel"
31+ , Tabs.tabPanel { id: "music" } "Music panel"
3332 ]
3433 ]
3534
test/HeroUI/Toast/Stories.purs+2−3
@@ -8,15 +8,14 @@ import HeroUI.Button as Btn
88import HeroUI.Toast as Toast
99import Yoga.React (component)
1010import Yoga.React.DOM.HTML (div)
11−import Yoga.React.DOM.Internal (text)
1211import YogaStories.Story (story)
1312
1413mkToast :: { message :: String } -> JSX
1514mkToast = component "ToastStory" \props -> React.do
1615 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1716 [ Toast.toastProvider { placement: "bottom-right" } ([] :: Array JSX)
18− , Btn.button { variant: Btn.Primary, onPress: handler_ (void (Toast.toastSuccess (text props.message))) }
19− (text "Show toast")
17+ , Btn.button { variant: Btn.Primary, onPress: handler_ (void (Toast.toastSuccess props.message)) }
18+ "Show toast"
2019 ]
2120
2221basic :: JSX
test/HeroUI/Tooltip/Stories.purs+2−3
@@ -7,15 +7,14 @@ import HeroUI.Button as Btn
77import HeroUI.Tooltip as Tooltip
88import Yoga.React (component)
99import Yoga.React.DOM.HTML (div)
10−import Yoga.React.DOM.Internal (text)
1110import YogaStories.Story (story)
1211
1312mkTooltip :: { tip :: String } -> JSX
1413mkTooltip = component "TooltipStory" \props -> React.do
1514 pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
1615 [ Tooltip.tooltip {}
17− [ Tooltip.tooltipTrigger {} [ Btn.button { variant: Btn.Primary } (text "Hover me") ]
18− , Tooltip.tooltipContent { placement: "top", showArrow: true } (text props.tip)
16+ [ Tooltip.tooltipTrigger {} [ Btn.button { variant: Btn.Primary } "Hover me" ]
17+ , Tooltip.tooltipContent { placement: "top", showArrow: true } props.tip
1918 ]
2019 ]
2120
yoga-stories-head.js+5
@@ -1 +1,6 @@
11import "./heroui-styles.css"
2+
3+// HeroUI v3 themes via a `dark` class on an ancestor. Overlays (popover,
4+// modal, dropdown) portal to <body>, outside any per-story wrapper, so set
5+// the class on the document root to keep portaled content on-theme.
6+document.documentElement.classList.add("dark")