git.iko.soy/rowtype-yoga/purescript-yoga-heroui3
on main help sign in
50 files changed+1097−18
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+28 test/HeroUI/Alert/Stories.purs+40 test/HeroUI/Autocomplete/Stories.purs+38 test/HeroUI/Avatar/Stories.purs+20 test/HeroUI/Badge/Stories.purs+23 test/HeroUI/Breadcrumbs/Stories.purs+22 test/HeroUI/Button/Stories.purs+1−2 test/HeroUI/Calendar/Stories.purs+17 test/HeroUI/Card/Stories.purs+36 test/HeroUI/Checkbox/Stories.purs+18 test/HeroUI/Chip/Stories.purs+31 test/HeroUI/DateField/Stories.purs+17 test/HeroUI/DatePicker/Stories.purs+21 test/HeroUI/Drawer/Stories.purs+30 test/HeroUI/Dropdown/Stories.purs+29 test/HeroUI/Form/Stories.purs+24 test/HeroUI/Input/Stories.purs+31 test/HeroUI/InputOTP/Stories.purs+22 test/HeroUI/Kbd/Stories.purs+21 test/HeroUI/Link/Stories.purs+17 test/HeroUI/ListBox/Stories.purs+23 test/HeroUI/Menu/Stories.purs+23 test/HeroUI/Modal/Stories.purs+30 test/HeroUI/NumberField/Stories.purs+24 test/HeroUI/Pagination/Stories.purs+27 test/HeroUI/Popover/Stories.purs+26 test/HeroUI/Progress/Stories.purs+20 test/HeroUI/Radio/Stories.purs+23 test/HeroUI/ScrollShadow/Stories.purs+22 test/HeroUI/Select/Stories.purs+28 test/HeroUI/Separator/Stories.purs+22 test/HeroUI/Skeleton/Stories.purs+31 test/HeroUI/Slider/Stories.purs+24 test/HeroUI/Spinner/Stories.purs+31 test/HeroUI/Switch/Stories.purs+18 test/HeroUI/Table/Stories.purs+37 test/HeroUI/Tabs/Stories.purs+36 test/HeroUI/Toast/Stories.purs+22 test/HeroUI/Tooltip/Stories.purs+22 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.pursnew file+28
@@ -0,0 +1,28 @@
1+module HeroUI.Accordion.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Accordion as Accordion
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+item :: String -> String -> String -> JSX
12+item key heading body =
13+ Accordion.accordionItem { id: key }
14+ [ Accordion.accordionHeading {} [ Accordion.accordionTrigger {} heading ]
15+ , Accordion.accordionPanel {} [ Accordion.accordionBody {} body ]
16+ ]
17+
18+mkAccordion :: {} -> JSX
19+mkAccordion = component "AccordionStory" \(_ :: {}) -> React.do
20+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-80" }
21+ [ Accordion.accordion { variant: Accordion.Default, defaultExpandedKeys: [ "a" ] }
22+ [ item "a" "First" "First panel body."
23+ , item "b" "Second" "Second panel body."
24+ ]
25+ ]
26+
27+basic :: JSX
28+basic = story "basic" mkAccordion {}
test/HeroUI/Alert/Stories.pursnew file+40
@@ -0,0 +1,40 @@
1+module HeroUI.Alert.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Generic.Rep (class Generic)
6+import React.Basic (JSX)
7+import HeroUI.Alert as Alert
8+import HeroUI.Types as T
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Controls (enum)
12+import YogaStories.Story (story)
13+
14+data Status = Default | Accent | Success | Warning | Danger
15+
16+derive instance Generic Status _
17+
18+toStatus :: Status -> T.Color
19+toStatus = case _ of
20+ Default -> T.Default
21+ Accent -> T.Accent
22+ Success -> T.Success
23+ Warning -> T.Warning
24+ Danger -> T.Danger
25+
26+mkAlert :: { title :: String, body :: String, status :: Status } -> JSX
27+mkAlert = component "AlertStory" \props -> React.do
28+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-96" }
29+ [ Alert.alert { status: toStatus props.status }
30+ [ Alert.alertIndicator {} ([] :: Array JSX)
31+ , Alert.alertContent {}
32+ [ Alert.alertTitle {} props.title
33+ , Alert.alertDescription {} props.body
34+ ]
35+ ]
36+ ]
37+
38+basic :: JSX
39+basic = story "basic" mkAlert
40+ { title: "Heads up", body: "Your changes were saved.", status: enum Success }
test/HeroUI/Autocomplete/Stories.pursnew file+38
@@ -0,0 +1,38 @@
1+module HeroUI.Autocomplete.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Autocomplete as Autocomplete
7+import HeroUI.Input as Input
8+import HeroUI.ListBox as ListBox
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Story (story)
12+
13+mkAutocomplete :: { placeholder :: String } -> JSX
14+mkAutocomplete = component "AutocompleteStory" \props -> React.do
15+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-64" }
16+ [ Autocomplete.autocomplete { placeholder: props.placeholder, "aria-label": "Fruit" }
17+ [ Autocomplete.autocompleteTrigger {}
18+ [ Autocomplete.autocompleteValue { placeholder: props.placeholder } ([] :: Array JSX)
19+ , Autocomplete.autocompleteIndicator {} ([] :: Array JSX)
20+ ]
21+ , Autocomplete.autocompletePopover {}
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+ ]
32+ ]
33+ ]
34+ ]
35+ ]
36+
37+basic :: JSX
38+basic = story "basic" mkAutocomplete { placeholder: "Search fruit" }
test/HeroUI/Avatar/Stories.pursnew file+20
@@ -0,0 +1,20 @@
1+module HeroUI.Avatar.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Avatar as Avatar
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkAvatar :: { initials :: String } -> JSX
13+mkAvatar = component "AvatarStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
15+ [ Avatar.avatar { color: T.Accent, size: T.Md, variant: Avatar.Soft }
16+ [ Avatar.avatarFallback { color: T.Accent } props.initials ]
17+ ]
18+
19+basic :: JSX
20+basic = story "basic" mkAvatar { initials: "ME" }
test/HeroUI/Badge/Stories.pursnew file+23
@@ -0,0 +1,23 @@
1+module HeroUI.Badge.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Avatar as Avatar
7+import HeroUI.Badge as Badge
8+import HeroUI.Types as T
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Story (story)
12+
13+mkBadge :: { count :: String } -> JSX
14+mkBadge = component "BadgeStory" \props -> React.do
15+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
16+ [ Badge.badge { color: T.Danger, variant: Badge.Primary, placement: Badge.TopRight, size: T.Md }
17+ [ Badge.badgeLabel {} props.count
18+ , Badge.badgeAnchor {} [ Avatar.avatar { color: T.Accent } [ Avatar.avatarFallback {} "ME" ] ]
19+ ]
20+ ]
21+
22+basic :: JSX
23+basic = story "basic" mkBadge { count: "5" }
test/HeroUI/Breadcrumbs/Stories.pursnew file+22
@@ -0,0 +1,22 @@
1+module HeroUI.Breadcrumbs.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Breadcrumbs as Breadcrumbs
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+mkBreadcrumbs :: {} -> JSX
12+mkBreadcrumbs = component "BreadcrumbsStory" \(_ :: {}) -> React.do
13+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
14+ [ Breadcrumbs.breadcrumbs {}
15+ [ Breadcrumbs.breadcrumbsItem { href: "#" } "Home"
16+ , Breadcrumbs.breadcrumbsItem { href: "#" } "Library"
17+ , Breadcrumbs.breadcrumbsItem {} "Current"
18+ ]
19+ ]
20+
21+basic :: JSX
22+basic = story "basic" mkBreadcrumbs {}
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/Calendar/Stories.pursnew file+17
@@ -0,0 +1,17 @@
1+module HeroUI.Calendar.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Calendar as Calendar
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+mkCalendar :: {} -> JSX
12+mkCalendar = component "CalendarStory" \(_ :: {}) -> React.do
13+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
14+ [ Calendar.calendar {} ([] :: Array JSX) ]
15+
16+basic :: JSX
17+basic = story "basic" mkCalendar {}
test/HeroUI/Card/Stories.pursnew file+36
@@ -0,0 +1,36 @@
1+module HeroUI.Card.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Generic.Rep (class Generic)
6+import React.Basic (JSX)
7+import HeroUI.Card as Card
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Controls (enum)
11+import YogaStories.Story (story)
12+
13+data Variant = Default | Secondary | Tertiary | Transparent
14+
15+derive instance Generic Variant _
16+
17+toVariant :: Variant -> Card.Variant
18+toVariant = case _ of
19+ Default -> Card.Default
20+ Secondary -> Card.Secondary
21+ Tertiary -> Card.Tertiary
22+ Transparent -> Card.Transparent
23+
24+mkCard :: { title :: String, body :: String, variant :: Variant } -> JSX
25+mkCard = component "CardStory" \props -> React.do
26+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
27+ [ Card.card { variant: toVariant props.variant, className: "max-w-sm" }
28+ [ Card.cardHeader {} [ Card.cardTitle {} props.title ]
29+ , Card.cardContent {} props.body
30+ , Card.cardFooter {} "Footer"
31+ ]
32+ ]
33+
34+basic :: JSX
35+basic = story "basic" mkCard
36+ { title: "Card title", body: "Some descriptive body text.", variant: enum Default }
test/HeroUI/Checkbox/Stories.pursnew file+18
@@ -0,0 +1,18 @@
1+module HeroUI.Checkbox.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Checkbox as Checkbox
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkCheckbox :: { label :: String, defaultSelected :: Boolean } -> JSX
13+mkCheckbox = component "CheckboxStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ Checkbox.checkbox { defaultSelected: props.defaultSelected, variant: T.Primary } props.label ]
16+
17+basic :: JSX
18+basic = story "basic" mkCheckbox { label: "Accept terms", defaultSelected: false }
test/HeroUI/Chip/Stories.pursnew file+31
@@ -0,0 +1,31 @@
1+module HeroUI.Chip.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Generic.Rep (class Generic)
6+import React.Basic (JSX)
7+import HeroUI.Chip as Chip
8+import HeroUI.Types as T
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Controls (enum)
12+import YogaStories.Story (story)
13+
14+data Variant = Primary | Secondary | Tertiary | Soft
15+
16+derive instance Generic Variant _
17+
18+toVariant :: Variant -> Chip.Variant
19+toVariant = case _ of
20+ Primary -> Chip.Primary
21+ Secondary -> Chip.Secondary
22+ Tertiary -> Chip.Tertiary
23+ Soft -> Chip.Soft
24+
25+mkChip :: { label :: String, variant :: Variant } -> JSX
26+mkChip = component "ChipStory" \props -> React.do
27+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
28+ [ Chip.chip { color: T.Accent, size: T.Md, variant: toVariant props.variant } props.label ]
29+
30+basic :: JSX
31+basic = story "basic" mkChip { label: "new", variant: enum Soft }
test/HeroUI/DateField/Stories.pursnew file+17
@@ -0,0 +1,17 @@
1+module HeroUI.DateField.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.DateField as DateField
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+mkDateField :: {} -> JSX
12+mkDateField = component "DateFieldStory" \(_ :: {}) -> React.do
13+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-72" }
14+ [ DateField.dateField { granularity: DateField.Day } ([] :: Array JSX) ]
15+
16+basic :: JSX
17+basic = story "basic" mkDateField {}
test/HeroUI/DatePicker/Stories.pursnew file+21
@@ -0,0 +1,21 @@
1+module HeroUI.DatePicker.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.DatePicker as DatePicker
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+mkDatePicker :: {} -> JSX
12+mkDatePicker = component "DatePickerStory" \(_ :: {}) -> React.do
13+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-72" }
14+ [ DatePicker.datePicker {}
15+ [ DatePicker.datePickerTrigger {} "Pick a date"
16+ , DatePicker.datePickerPopover {} ([] :: Array JSX)
17+ ]
18+ ]
19+
20+basic :: JSX
21+basic = story "basic" mkDatePicker {}
test/HeroUI/Drawer/Stories.pursnew file+30
@@ -0,0 +1,30 @@
1+module HeroUI.Drawer.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Button as Btn
7+import HeroUI.Drawer as Drawer
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkDrawer :: { title :: String } -> JSX
13+mkDrawer = component "DrawerStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ Drawer.drawer {}
16+ [ Btn.button { variant: Btn.Primary } "Open drawer"
17+ , Drawer.drawerBackdrop { variant: Drawer.Blur }
18+ [ Drawer.drawerContent { placement: Drawer.Right }
19+ [ Drawer.drawerDialog {}
20+ [ Drawer.drawerHeader {} [ Drawer.drawerHeading {} props.title ]
21+ , Drawer.drawerBody {} "Drawer body content."
22+ , Drawer.drawerFooter {} [ Drawer.drawerCloseTrigger {} "Close" ]
23+ ]
24+ ]
25+ ]
26+ ]
27+ ]
28+
29+basic :: JSX
30+basic = story "basic" mkDrawer { title: "Settings" }
test/HeroUI/Dropdown/Stories.pursnew file+29
@@ -0,0 +1,29 @@
1+module HeroUI.Dropdown.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Button as Btn
7+import HeroUI.Dropdown as Dropdown
8+import HeroUI.Types as T
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Story (story)
12+
13+mkDropdown :: {} -> JSX
14+mkDropdown = component "DropdownStory" \(_ :: {}) -> React.do
15+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
16+ [ Dropdown.dropdown {}
17+ [ Dropdown.dropdownTrigger {} [ Btn.button { variant: Btn.Primary } "Actions" ]
18+ , Dropdown.dropdownPopover {}
19+ [ Dropdown.dropdownMenu { selectionMode: T.SelectionNone }
20+ [ Dropdown.dropdownItem { id: "new" } "New file"
21+ , Dropdown.dropdownItem { id: "copy" } "Copy link"
22+ , Dropdown.dropdownItem { id: "delete" } "Delete"
23+ ]
24+ ]
25+ ]
26+ ]
27+
28+basic :: JSX
29+basic = story "basic" mkDropdown {}
test/HeroUI/Form/Stories.pursnew file+24
@@ -0,0 +1,24 @@
1+module HeroUI.Form.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Button as Btn
7+import HeroUI.Form as Form
8+import HeroUI.Input as Input
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Story (story)
12+
13+mkForm :: {} -> JSX
14+mkForm = component "FormStory" \(_ :: {}) -> React.do
15+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-72" }
16+ [ Form.form { validationBehavior: Form.Native, className: "flex flex-col gap-3" }
17+ [ Input.textField { name: "email" }
18+ [ Input.input { placeholder: "you@example.com", type: Input.InputTypeEmail } ([] :: Array JSX) ]
19+ , Btn.button { type: Btn.ButtonTypeSubmit, variant: Btn.Primary } "Submit"
20+ ]
21+ ]
22+
23+basic :: JSX
24+basic = story "basic" mkForm {}
test/HeroUI/Input/Stories.pursnew file+31
@@ -0,0 +1,31 @@
1+module HeroUI.Input.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Generic.Rep (class Generic)
6+import React.Basic (JSX)
7+import HeroUI.Input as Input
8+import HeroUI.Types as T
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Controls (enum)
12+import YogaStories.Story (story)
13+
14+data Variant = Primary | Secondary
15+
16+derive instance Generic Variant _
17+
18+toVariant :: Variant -> T.FieldVariant
19+toVariant = case _ of
20+ Primary -> T.Primary
21+ Secondary -> T.Secondary
22+
23+mkInput :: { placeholder :: String, variant :: Variant } -> JSX
24+mkInput = component "InputStory" \props -> React.do
25+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-72" }
26+ [ Input.textField { variant: toVariant props.variant }
27+ [ Input.input { placeholder: props.placeholder, type: Input.InputTypeText } ([] :: Array JSX) ]
28+ ]
29+
30+basic :: JSX
31+basic = story "basic" mkInput { placeholder: "Type here…", variant: enum Primary }
test/HeroUI/InputOTP/Stories.pursnew file+22
@@ -0,0 +1,22 @@
1+module HeroUI.InputOTP.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Array ((..))
6+import React.Basic (JSX)
7+import HeroUI.InputOTP as InputOTP
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkInputOTP :: { length :: Int } -> JSX
13+mkInputOTP = component "InputOTPStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ InputOTP.inputOTP { maxLength: props.length }
16+ [ InputOTP.inputOTPGroup {}
17+ (map (\i -> InputOTP.inputOTPSlot { index: i } ([] :: Array JSX)) (0 .. (props.length - 1)))
18+ ]
19+ ]
20+
21+basic :: JSX
22+basic = story "basic" mkInputOTP { length: 4 }
test/HeroUI/Kbd/Stories.pursnew file+21
@@ -0,0 +1,21 @@
1+module HeroUI.Kbd.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Kbd as Kbd
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+mkKbd :: { label :: String } -> JSX
12+mkKbd = component "KbdStory" \props -> React.do
13+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-2" }
14+ [ Kbd.kbd { variant: Kbd.Default }
15+ [ Kbd.kbdAbbr { keyValue: Kbd.Command } ([] :: Array JSX)
16+ , Kbd.kbdContent {} props.label
17+ ]
18+ ]
19+
20+basic :: JSX
21+basic = story "basic" mkKbd { label: "K" }
test/HeroUI/Link/Stories.pursnew file+17
@@ -0,0 +1,17 @@
1+module HeroUI.Link.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Link as Link
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+mkLink :: { label :: String, href :: String } -> JSX
12+mkLink = component "LinkStory" \props -> React.do
13+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
14+ [ Link.link { href: props.href, target: "_blank" } props.label ]
15+
16+basic :: JSX
17+basic = story "basic" mkLink { label: "Visit HeroUI", href: "https://heroui.com" }
test/HeroUI/ListBox/Stories.pursnew file+23
@@ -0,0 +1,23 @@
1+module HeroUI.ListBox.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.ListBox as ListBox
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkListBox :: {} -> JSX
13+mkListBox = component "ListBoxStory" \(_ :: {}) -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-56" }
15+ [ ListBox.listBox { selectionMode: T.Multiple, defaultSelectedKeys: [ "a" ] }
16+ [ ListBox.listBoxItem { id: "a" } "Apple"
17+ , ListBox.listBoxItem { id: "b" } "Banana"
18+ , ListBox.listBoxItem { id: "c" } "Cherry"
19+ ]
20+ ]
21+
22+basic :: JSX
23+basic = story "basic" mkListBox {}
test/HeroUI/Menu/Stories.pursnew file+23
@@ -0,0 +1,23 @@
1+module HeroUI.Menu.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Menu as Menu
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkMenu :: {} -> JSX
13+mkMenu = component "MenuStory" \(_ :: {}) -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-56" }
15+ [ Menu.menu { selectionMode: T.Single, defaultSelectedKeys: [ "profile" ] }
16+ [ Menu.menuItem { id: "profile" } "Profile"
17+ , Menu.menuItem { id: "settings" } "Settings"
18+ , Menu.menuItem { id: "logout" } "Log out"
19+ ]
20+ ]
21+
22+basic :: JSX
23+basic = story "basic" mkMenu {}
test/HeroUI/Modal/Stories.pursnew file+30
@@ -0,0 +1,30 @@
1+module HeroUI.Modal.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Button as Btn
7+import HeroUI.Modal as Modal
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkModal :: { title :: String } -> JSX
13+mkModal = component "ModalStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ Modal.modal {}
16+ [ Btn.button { variant: Btn.Primary } "Open modal"
17+ , Modal.modalBackdrop { variant: Modal.Blur }
18+ [ Modal.modalContainer { placement: Modal.PlacementCenter, size: Modal.Md }
19+ [ Modal.modalDialog {}
20+ [ Modal.modalHeader {} [ Modal.modalHeading {} props.title ]
21+ , Modal.modalBody {} "Modal body content."
22+ , Modal.modalFooter {} [ Modal.modalCloseTrigger {} "Close" ]
23+ ]
24+ ]
25+ ]
26+ ]
27+ ]
28+
29+basic :: JSX
30+basic = story "basic" mkModal { title: "Dialog" }
test/HeroUI/NumberField/Stories.pursnew file+24
@@ -0,0 +1,24 @@
1+module HeroUI.NumberField.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.NumberField as NumberField
7+import Yoga.React (component)
8+import Yoga.React.DOM.HTML (div)
9+import YogaStories.Story (story)
10+
11+mkNumberField :: { defaultValue :: Number } -> JSX
12+mkNumberField = component "NumberFieldStory" \props -> React.do
13+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-60" }
14+ [ NumberField.numberField { defaultValue: props.defaultValue, minValue: 0.0, maxValue: 100.0, step: 1.0 }
15+ [ NumberField.numberFieldGroup {}
16+ [ NumberField.numberFieldDecrementButton {} "−"
17+ , NumberField.numberFieldInput {} ([] :: Array JSX)
18+ , NumberField.numberFieldIncrementButton {} "+"
19+ ]
20+ ]
21+ ]
22+
23+basic :: JSX
24+basic = story "basic" mkNumberField { defaultValue: 5.0 }
test/HeroUI/Pagination/Stories.pursnew file+27
@@ -0,0 +1,27 @@
1+module HeroUI.Pagination.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Pagination as Pagination
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkPagination :: {} -> JSX
13+mkPagination = component "PaginationStory" \(_ :: {}) -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ Pagination.pagination { size: T.Md }
16+ [ Pagination.paginationContent {}
17+ [ Pagination.paginationPrevious {} "‹"
18+ , Pagination.paginationItem { isActive: true } "1"
19+ , Pagination.paginationItem { isActive: false } "2"
20+ , Pagination.paginationItem { isActive: false } "3"
21+ , Pagination.paginationNext {} "›"
22+ ]
23+ ]
24+ ]
25+
26+basic :: JSX
27+basic = story "basic" mkPagination {}
test/HeroUI/Popover/Stories.pursnew file+26
@@ -0,0 +1,26 @@
1+module HeroUI.Popover.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Button as Btn
7+import HeroUI.Popover as Popover
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import Yoga.React.DOM.Internal (text)
11+import YogaStories.Story (story)
12+
13+mkPopover :: {} -> JSX
14+mkPopover = component "PopoverStory" \(_ :: {}) -> React.do
15+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
16+ [ Popover.popover {}
17+ [ Popover.popoverTrigger {} [ Btn.button { variant: Btn.Primary } "Open popover" ]
18+ , Popover.popoverContent { placement: "bottom", showArrow: true }
19+ [ Popover.popoverHeading {} "Heading"
20+ , text "Popover content."
21+ ]
22+ ]
23+ ]
24+
25+basic :: JSX
26+basic = story "basic" mkPopover {}
test/HeroUI/Progress/Stories.pursnew file+20
@@ -0,0 +1,20 @@
1+module HeroUI.Progress.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Progress as Progress
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkProgress :: { value :: Number } -> JSX
13+mkProgress = component "ProgressStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex flex-col gap-4 w-72" }
15+ [ Progress.progressBar { value: props.value, minValue: 0.0, maxValue: 100.0, color: T.Accent, size: T.Md } ([] :: Array JSX)
16+ , Progress.progressCircle { value: props.value, minValue: 0.0, maxValue: 100.0, color: T.Success, size: T.Lg } ([] :: Array JSX)
17+ ]
18+
19+basic :: JSX
20+basic = story "basic" mkProgress { value: 60.0 }
test/HeroUI/Radio/Stories.pursnew file+23
@@ -0,0 +1,23 @@
1+module HeroUI.Radio.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Radio as Radio
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkRadio :: { defaultValue :: String } -> JSX
13+mkRadio = component "RadioStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ Radio.radioGroup { defaultValue: props.defaultValue, orientation: T.Vertical, variant: T.Primary }
16+ [ Radio.radio { value: "a" } "Option A"
17+ , Radio.radio { value: "b" } "Option B"
18+ , Radio.radio { value: "c" } "Option C"
19+ ]
20+ ]
21+
22+basic :: JSX
23+basic = story "basic" mkRadio { defaultValue: "a" }
test/HeroUI/ScrollShadow/Stories.pursnew file+22
@@ -0,0 +1,22 @@
1+module HeroUI.ScrollShadow.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Array (replicate)
6+import React.Basic (JSX)
7+import HeroUI.ScrollShadow as ScrollShadow
8+import HeroUI.Types as T
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import Yoga.React.DOM.Internal (text)
12+import YogaStories.Story (story)
13+
14+mkScrollShadow :: {} -> JSX
15+mkScrollShadow = component "ScrollShadowStory" \(_ :: {}) -> React.do
16+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
17+ [ ScrollShadow.scrollShadow { orientation: T.Vertical, className: "h-40 w-64" }
18+ (replicate 20 (div { className: "py-1" } [ text "Scrollable line" ]))
19+ ]
20+
21+basic :: JSX
22+basic = story "basic" mkScrollShadow {}
test/HeroUI/Select/Stories.pursnew file+28
@@ -0,0 +1,28 @@
1+module HeroUI.Select.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.ListBox as ListBox
7+import HeroUI.Select as Select
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkSelect :: { placeholder :: String } -> JSX
13+mkSelect = component "SelectStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-64" }
15+ [ Select.select { placeholder: props.placeholder, defaultSelectedKey: "apple" }
16+ [ Select.selectTrigger {} [ Select.selectValue { placeholder: props.placeholder } ([] :: Array JSX) ]
17+ , Select.selectPopover {}
18+ [ ListBox.listBox {}
19+ [ ListBox.listBoxItem { id: "apple" } "Apple"
20+ , ListBox.listBoxItem { id: "banana" } "Banana"
21+ , ListBox.listBoxItem { id: "cherry" } "Cherry"
22+ ]
23+ ]
24+ ]
25+ ]
26+
27+basic :: JSX
28+basic = story "basic" mkSelect { placeholder: "Pick a fruit" }
test/HeroUI/Separator/Stories.pursnew file+22
@@ -0,0 +1,22 @@
1+module HeroUI.Separator.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Separator as Separator
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import Yoga.React.DOM.Internal (text)
11+import YogaStories.Story (story)
12+
13+mkSeparator :: {} -> JSX
14+mkSeparator = component "SeparatorStory" \(_ :: {}) -> React.do
15+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex flex-col gap-3 w-64" }
16+ [ text "Above"
17+ , Separator.separator { orientation: T.Horizontal, variant: Separator.Default } ([] :: Array JSX)
18+ , text "Below"
19+ ]
20+
21+basic :: JSX
22+basic = story "basic" mkSeparator {}
test/HeroUI/Skeleton/Stories.pursnew file+31
@@ -0,0 +1,31 @@
1+module HeroUI.Skeleton.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Generic.Rep (class Generic)
6+import React.Basic (JSX)
7+import HeroUI.Skeleton as Skeleton
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Controls (enum)
11+import YogaStories.Story (story)
12+
13+data Animation = None | Pulse | Shimmer
14+
15+derive instance Generic Animation _
16+
17+toAnimation :: Animation -> Skeleton.AnimationType
18+toAnimation = case _ of
19+ None -> Skeleton.None
20+ Pulse -> Skeleton.Pulse
21+ Shimmer -> Skeleton.Shimmer
22+
23+mkSkeleton :: { animation :: Animation } -> JSX
24+mkSkeleton = component "SkeletonStory" \props -> React.do
25+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex flex-col gap-3 w-64" }
26+ [ Skeleton.skeleton { animationType: toAnimation props.animation, className: "h-4 w-full rounded" } ([] :: Array JSX)
27+ , Skeleton.skeleton { animationType: toAnimation props.animation, className: "h-4 w-3/4 rounded" } ([] :: Array JSX)
28+ ]
29+
30+basic :: JSX
31+basic = story "basic" mkSkeleton { animation: enum Pulse }
test/HeroUI/Slider/Stories.pursnew file+24
@@ -0,0 +1,24 @@
1+module HeroUI.Slider.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Slider as Slider
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkSlider :: { defaultValue :: Number } -> JSX
13+mkSlider = component "SliderStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-72" }
15+ [ Slider.slider { defaultValue: props.defaultValue, minValue: 0.0, maxValue: 100.0, step: 1.0, orientation: T.Horizontal }
16+ [ Slider.sliderTrack {}
17+ [ Slider.sliderFill {} ([] :: Array JSX)
18+ , Slider.sliderThumb {} ([] :: Array JSX)
19+ ]
20+ ]
21+ ]
22+
23+basic :: JSX
24+basic = story "basic" mkSlider { defaultValue: 40.0 }
test/HeroUI/Spinner/Stories.pursnew file+31
@@ -0,0 +1,31 @@
1+module HeroUI.Spinner.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Generic.Rep (class Generic)
6+import React.Basic (JSX)
7+import HeroUI.Spinner as Spinner
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Controls (enum)
11+import YogaStories.Story (story)
12+
13+data Color = Accent | Current | Success | Warning | Danger
14+
15+derive instance Generic Color _
16+
17+toColor :: Color -> Spinner.Color
18+toColor = case _ of
19+ Accent -> Spinner.Accent
20+ Current -> Spinner.Current
21+ Success -> Spinner.Success
22+ Warning -> Spinner.Warning
23+ Danger -> Spinner.Danger
24+
25+mkSpinner :: { color :: Color } -> JSX
26+mkSpinner = component "SpinnerStory" \props -> React.do
27+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg flex items-center gap-4" }
28+ [ Spinner.spinner { color: toColor props.color, size: Spinner.Lg } ([] :: Array JSX) ]
29+
30+basic :: JSX
31+basic = story "basic" mkSpinner { color: enum Accent }
test/HeroUI/Switch/Stories.pursnew file+18
@@ -0,0 +1,18 @@
1+module HeroUI.Switch.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Switch as Switch
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkSwitch :: { label :: String, defaultSelected :: Boolean } -> JSX
13+mkSwitch = component "SwitchStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ Switch.switch_ { defaultSelected: props.defaultSelected, size: T.Md } props.label ]
16+
17+basic :: JSX
18+basic = story "basic" mkSwitch { label: "Wi-Fi", defaultSelected: true }
test/HeroUI/Table/Stories.pursnew file+37
@@ -0,0 +1,37 @@
1+module HeroUI.Table.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Table as Table
7+import HeroUI.Types as T
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+row :: String -> String -> String -> JSX
13+row key name role =
14+ Table.tableRow { id: key }
15+ [ Table.tableCell {} name
16+ , Table.tableCell {} role
17+ ]
18+
19+mkTable :: {} -> JSX
20+mkTable = component "TableStory" \(_ :: {}) -> React.do
21+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
22+ [ Table.table {}
23+ [ Table.tableContent { selectionMode: T.Multiple, "aria-label": "People" }
24+ [ Table.tableHeader {}
25+ [ Table.tableColumn { id: "name", isRowHeader: true } "Name"
26+ , Table.tableColumn { id: "role" } "Role"
27+ ]
28+ , Table.tableBody {}
29+ [ row "1" "Alice" "Admin"
30+ , row "2" "Bob" "User"
31+ ]
32+ ]
33+ ]
34+ ]
35+
36+basic :: JSX
37+basic = story "basic" mkTable {}
test/HeroUI/Tabs/Stories.pursnew file+36
@@ -0,0 +1,36 @@
1+module HeroUI.Tabs.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import Data.Generic.Rep (class Generic)
6+import React.Basic (JSX)
7+import HeroUI.Tabs as Tabs
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Controls (enum)
11+import YogaStories.Story (story)
12+
13+data Variant = Primary | Secondary
14+
15+derive instance Generic Variant _
16+
17+toVariant :: Variant -> Tabs.Variant
18+toVariant = case _ of
19+ Primary -> Tabs.Primary
20+ Secondary -> Tabs.Secondary
21+
22+mkTabs :: { variant :: Variant } -> JSX
23+mkTabs = component "TabsStory" \props -> React.do
24+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg w-96" }
25+ [ Tabs.tabs { variant: toVariant props.variant, defaultSelectedKey: "photos" }
26+ [ Tabs.tabList {}
27+ [ Tabs.tab { id: "photos" } "Photos"
28+ , Tabs.tab { id: "music" } "Music"
29+ ]
30+ , Tabs.tabPanel { id: "photos" } "Photos panel"
31+ , Tabs.tabPanel { id: "music" } "Music panel"
32+ ]
33+ ]
34+
35+basic :: JSX
36+basic = story "basic" mkTabs { variant: enum Primary }
test/HeroUI/Toast/Stories.pursnew file+22
@@ -0,0 +1,22 @@
1+module HeroUI.Toast.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import React.Basic.Events (handler_)
7+import HeroUI.Button as Btn
8+import HeroUI.Toast as Toast
9+import Yoga.React (component)
10+import Yoga.React.DOM.HTML (div)
11+import YogaStories.Story (story)
12+
13+mkToast :: { message :: String } -> JSX
14+mkToast = component "ToastStory" \props -> React.do
15+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
16+ [ Toast.toastProvider { placement: "bottom-right" } ([] :: Array JSX)
17+ , Btn.button { variant: Btn.Primary, onPress: handler_ (void (Toast.toastSuccess props.message)) }
18+ "Show toast"
19+ ]
20+
21+basic :: JSX
22+basic = story "basic" mkToast { message: "Saved!" }
test/HeroUI/Tooltip/Stories.pursnew file+22
@@ -0,0 +1,22 @@
1+module HeroUI.Tooltip.Stories (basic) where
2+
3+import Prelude hiding (div)
4+
5+import React.Basic (JSX)
6+import HeroUI.Button as Btn
7+import HeroUI.Tooltip as Tooltip
8+import Yoga.React (component)
9+import Yoga.React.DOM.HTML (div)
10+import YogaStories.Story (story)
11+
12+mkTooltip :: { tip :: String } -> JSX
13+mkTooltip = component "TooltipStory" \props -> React.do
14+ pure $ div { className: "dark bg-background text-foreground p-6 rounded-lg" }
15+ [ Tooltip.tooltip {}
16+ [ Tooltip.tooltipTrigger {} [ Btn.button { variant: Btn.Primary } "Hover me" ]
17+ , Tooltip.tooltipContent { placement: "top", showArrow: true } props.tip
18+ ]
19+ ]
20+
21+basic :: JSX
22+basic = story "basic" mkTooltip { tip: "Helpful hint" }
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")