diff --git a/.changeset/beige-sheep-chew.md b/.changeset/beige-sheep-chew.md new file mode 100644 index 00000000..c1f890af --- /dev/null +++ b/.changeset/beige-sheep-chew.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Card: Added gap, direction, align, justify props diff --git a/.changeset/bold-facts-fail.md b/.changeset/bold-facts-fail.md new file mode 100644 index 00000000..13edd7d4 --- /dev/null +++ b/.changeset/bold-facts-fail.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Radio: Disabled transition on keyboard mode diff --git a/.changeset/bumpy-oranges-trade.md b/.changeset/bumpy-oranges-trade.md new file mode 100644 index 00000000..717ac644 --- /dev/null +++ b/.changeset/bumpy-oranges-trade.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Overlay: Removed number support from transparency diff --git a/.changeset/clever-swans-bow.md b/.changeset/clever-swans-bow.md new file mode 100644 index 00000000..e8b1b28f --- /dev/null +++ b/.changeset/clever-swans-bow.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Tabs: Renamed pills-elevated to pills-raised and updated its design diff --git a/.changeset/config.json b/.changeset/config.json index 48ce41b8..592c9bc3 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -5,6 +5,6 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "fixed": [["reshaped", "@reshaped/headless", "@reshaped/utilities"]], + "fixed": [["reshaped", "@reshaped/utilities"]], "ignore": [] } diff --git a/.changeset/cool-tables-sip.md b/.changeset/cool-tables-sip.md new file mode 100644 index 00000000..226e46bf --- /dev/null +++ b/.changeset/cool-tables-sip.md @@ -0,0 +1,5 @@ +--- +"@reshaped/theming": major +--- + +Updated shadow tokens structure to base/raised/overlay and intense variant diff --git a/.changeset/crisp-fans-joke.md b/.changeset/crisp-fans-joke.md new file mode 100644 index 00000000..bdc7af06 --- /dev/null +++ b/.changeset/crisp-fans-joke.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Select: renderValue is required for multiselection diff --git a/.changeset/cyan-bars-sniff.md b/.changeset/cyan-bars-sniff.md new file mode 100644 index 00000000..3df957f3 --- /dev/null +++ b/.changeset/cyan-bars-sniff.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Card: renamed elevated to elevation prop with base, raised and overlay values diff --git a/.changeset/dark-eagles-jog.md b/.changeset/dark-eagles-jog.md new file mode 100644 index 00000000..0ac08010 --- /dev/null +++ b/.changeset/dark-eagles-jog.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Alert: Updated the icon size and neutral alert icon color diff --git a/.changeset/deep-ducks-sin.md b/.changeset/deep-ducks-sin.md new file mode 100644 index 00000000..c28475b9 --- /dev/null +++ b/.changeset/deep-ducks-sin.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Select: Moved to explicit trigger rendering with SelectTrigger instead of scanning for children diff --git a/.changeset/dirty-buckets-win.md b/.changeset/dirty-buckets-win.md new file mode 100644 index 00000000..5abe36f8 --- /dev/null +++ b/.changeset/dirty-buckets-win.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Button: Removed deprecated position property in aligner diff --git a/.changeset/dirty-rats-spend.md b/.changeset/dirty-rats-spend.md new file mode 100644 index 00000000..f747e74f --- /dev/null +++ b/.changeset/dirty-rats-spend.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Stepper: Deleted component diff --git a/.changeset/fifty-papayas-count.md b/.changeset/fifty-papayas-count.md new file mode 100644 index 00000000..032372bb --- /dev/null +++ b/.changeset/fifty-papayas-count.md @@ -0,0 +1,7 @@ +--- +"reshaped": major +--- + +Toast: Removed provider-level `toastOptions` and moved width control to per-toast `show()` calls. +Added `width` support to toast options with `short` and `long` presets (as well as custom CSS width values), where collapsed stacks use the latest toast width and expanded stacks restore each toast's original width. +Updated toast API by replacing `size` with `orientation` and removing `inverted` color. diff --git a/.changeset/flat-jeans-wait.md b/.changeset/flat-jeans-wait.md new file mode 100644 index 00000000..8ae4bf86 --- /dev/null +++ b/.changeset/flat-jeans-wait.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Select: selectedIconPosition diff --git a/.changeset/four-cases-slide.md b/.changeset/four-cases-slide.md new file mode 100644 index 00000000..2925e543 --- /dev/null +++ b/.changeset/four-cases-slide.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Button: Removed faded variant diff --git a/.changeset/gentle-frogs-cheer.md b/.changeset/gentle-frogs-cheer.md new file mode 100644 index 00000000..0269fd4b --- /dev/null +++ b/.changeset/gentle-frogs-cheer.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +FormControl: added gap property and improved default gaps diff --git a/.changeset/green-ads-sing.md b/.changeset/green-ads-sing.md new file mode 100644 index 00000000..89a6b607 --- /dev/null +++ b/.changeset/green-ads-sing.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Switch: Updated styles diff --git a/.changeset/green-jeans-tell.md b/.changeset/green-jeans-tell.md new file mode 100644 index 00000000..16c8d886 --- /dev/null +++ b/.changeset/green-jeans-tell.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Select: Removed Select.Custom and made custom rendering as default diff --git a/.changeset/hip-kiwis-turn.md b/.changeset/hip-kiwis-turn.md new file mode 100644 index 00000000..056ed35e --- /dev/null +++ b/.changeset/hip-kiwis-turn.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Card: Added borderRadius property diff --git a/.changeset/itchy-icons-switch.md b/.changeset/itchy-icons-switch.md new file mode 100644 index 00000000..a9801bd6 --- /dev/null +++ b/.changeset/itchy-icons-switch.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Popover: Applied new shadow/border mixins diff --git a/.changeset/jolly-pets-kiss.md b/.changeset/jolly-pets-kiss.md new file mode 100644 index 00000000..5194ea87 --- /dev/null +++ b/.changeset/jolly-pets-kiss.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Tabs: Added small variant diff --git a/.changeset/late-zebras-push.md b/.changeset/late-zebras-push.md new file mode 100644 index 00000000..64126088 --- /dev/null +++ b/.changeset/late-zebras-push.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Button: Updated colors for the outline button, improved spacing and button group styles diff --git a/.changeset/lazy-seas-enter.md b/.changeset/lazy-seas-enter.md new file mode 100644 index 00000000..d93479d0 --- /dev/null +++ b/.changeset/lazy-seas-enter.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +ActionBar: Removed from the library in favor of View diff --git a/.changeset/legal-regions-read.md b/.changeset/legal-regions-read.md new file mode 100644 index 00000000..105947fd --- /dev/null +++ b/.changeset/legal-regions-read.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Select: inputAttributes now correctly always apply to the input/select element, SelectTrigger got a new triggerAttributes prop for linking it with the flyout diff --git a/.changeset/lemon-berries-leave.md b/.changeset/lemon-berries-leave.md new file mode 100644 index 00000000..ba94f2b8 --- /dev/null +++ b/.changeset/lemon-berries-leave.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Breadcrumbs: Implemented gap rounding between the text and the icon diff --git a/.changeset/little-mangos-attack.md b/.changeset/little-mangos-attack.md new file mode 100644 index 00000000..50bc707e --- /dev/null +++ b/.changeset/little-mangos-attack.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Flyout: Removed deprecated forcePosition and fallbackMinWidth props diff --git a/.changeset/loud-houses-watch.md b/.changeset/loud-houses-watch.md new file mode 100644 index 00000000..a5127bfc --- /dev/null +++ b/.changeset/loud-houses-watch.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Carousel: Dropped CarouselInstanceRef in favor of CarouselInstance type diff --git a/.changeset/many-moles-end.md b/.changeset/many-moles-end.md new file mode 100644 index 00000000..7b594ae6 --- /dev/null +++ b/.changeset/many-moles-end.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Select: Automatically match the select size and options size diff --git a/.changeset/moody-lands-lead.md b/.changeset/moody-lands-lead.md new file mode 100644 index 00000000..17464e9c --- /dev/null +++ b/.changeset/moody-lands-lead.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Overlay: Increased animation speed diff --git a/.changeset/ninety-worms-return.md b/.changeset/ninety-worms-return.md new file mode 100644 index 00000000..ac7f24f2 --- /dev/null +++ b/.changeset/ninety-worms-return.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Select: Updated styles to use latest tokens diff --git a/.changeset/open-peas-work.md b/.changeset/open-peas-work.md new file mode 100644 index 00000000..d83a18d4 --- /dev/null +++ b/.changeset/open-peas-work.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Timeline: Deleted component diff --git a/.changeset/perky-eagles-wonder.md b/.changeset/perky-eagles-wonder.md new file mode 100644 index 00000000..7e4cad72 --- /dev/null +++ b/.changeset/perky-eagles-wonder.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Loader: Improved animation timing diff --git a/.changeset/petite-cooks-wish.md b/.changeset/petite-cooks-wish.md new file mode 100644 index 00000000..a0e5c44e --- /dev/null +++ b/.changeset/petite-cooks-wish.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +TextArea: Updated styles and fixed min-height when resizing to a single row diff --git a/.changeset/pink-cloths-brush.md b/.changeset/pink-cloths-brush.md new file mode 100644 index 00000000..abe2b5af --- /dev/null +++ b/.changeset/pink-cloths-brush.md @@ -0,0 +1,5 @@ +--- +"@reshaped/theming": patch +--- + +Added highlighted faded background colors to theme config diff --git a/.changeset/polite-doodles-enter.md b/.changeset/polite-doodles-enter.md new file mode 100644 index 00000000..cd0d887d --- /dev/null +++ b/.changeset/polite-doodles-enter.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Divider: renamed offset to inset and updated it to use unit tokens instead of string px values diff --git a/.changeset/pre.json b/.changeset/pre.json index cf4dcf87..38dfa841 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,17 +2,84 @@ "mode": "exit", "tag": "canary", "initialVersions": { - "@reshaped/headless": "3.11.0-canary.3", - "reshaped": "3.11.0-canary.3", - "@reshaped/utilities": "3.11.0-canary.3" + "reshaped": "4.0.0-canary.6", + "@reshaped/theming": "4.0.0-canary.17", + "@reshaped/utilities": "4.0.0-canary.6" }, "changesets": [ + "beige-sheep-chew", + "better-cycles-take", + "bold-facts-fail", "bright-eagles-cover", + "bumpy-oranges-trade", + "chatty-papayas-sink", + "clever-swans-bow", + "cool-tables-sip", + "crisp-fans-joke", + "cyan-bars-sniff", + "dark-eagles-jog", + "deep-ducks-sin", + "dirty-buckets-win", + "dirty-rats-spend", + "dull-melons-divide", + "fifty-papayas-count", + "flat-bars-return", + "flat-jeans-wait", + "four-cases-slide", + "gentle-frogs-cheer", + "green-ads-sing", + "green-jeans-tell", + "hip-kiwis-turn", + "itchy-icons-switch", + "jolly-pets-kiss", + "late-zebras-push", + "lazy-seas-enter", + "legal-regions-read", + "lemon-berries-leave", + "little-mangos-attack", + "loud-houses-watch", + "many-moles-end", + "moody-lands-lead", + "ninety-worms-return", + "open-peas-work", + "perky-eagles-wonder", + "petite-cooks-wish", + "pink-cloths-brush", "pink-olives-fry", "plenty-boxes-work", + "polite-doodles-enter", + "proud-baboons-camp", + "public-readers-add", + "purple-areas-hunt", "quick-dingos-stand", + "quick-sheep-dress", + "shaky-bottles-live", + "shaky-lamps-accept", + "shiny-chefs-peel", + "silly-plums-ask", + "silver-ants-cheer", + "silver-meals-arrive", + "six-pets-take", "sixty-queens-do", + "slick-symbols-love", + "slow-carpets-behave", "some-planes-kick", - "wise-goats-report" + "sour-lies-look", + "spicy-poets-make", + "sunny-words-say", + "tall-chefs-report", + "tall-schools-kick", + "tangy-papers-occur", + "tasty-animals-drive", + "thirty-socks-rule", + "three-dingos-chew", + "twelve-moles-check", + "twenty-chicken-lead", + "violet-rings-dig", + "warm-dingos-accept", + "warm-tigers-wait", + "wise-goats-report", + "wise-lands-cheer", + "young-meals-clap" ] } diff --git a/.changeset/proud-baboons-camp.md b/.changeset/proud-baboons-camp.md new file mode 100644 index 00000000..dadcb90d --- /dev/null +++ b/.changeset/proud-baboons-camp.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +MenuItem: Removed roundedCorners property diff --git a/.changeset/public-readers-add.md b/.changeset/public-readers-add.md new file mode 100644 index 00000000..43665d45 --- /dev/null +++ b/.changeset/public-readers-add.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Checkbox: Increase small checkbox gap diff --git a/.changeset/purple-areas-hunt.md b/.changeset/purple-areas-hunt.md new file mode 100644 index 00000000..6050694c --- /dev/null +++ b/.changeset/purple-areas-hunt.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +ProgressBar: Added neutral color diff --git a/.changeset/quick-sheep-dress.md b/.changeset/quick-sheep-dress.md new file mode 100644 index 00000000..143d09e2 --- /dev/null +++ b/.changeset/quick-sheep-dress.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Tooltip: Added border diff --git a/.changeset/shaky-bottles-live.md b/.changeset/shaky-bottles-live.md new file mode 100644 index 00000000..af153432 --- /dev/null +++ b/.changeset/shaky-bottles-live.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Scrim: Deleted component diff --git a/.changeset/shaky-lamps-accept.md b/.changeset/shaky-lamps-accept.md new file mode 100644 index 00000000..477079c4 --- /dev/null +++ b/.changeset/shaky-lamps-accept.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +View: improved keys handling diff --git a/.changeset/shiny-chefs-peel.md b/.changeset/shiny-chefs-peel.md new file mode 100644 index 00000000..271844a7 --- /dev/null +++ b/.changeset/shiny-chefs-peel.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +ScrollArea: Updated design and fixed resize bug diff --git a/.changeset/silly-plums-ask.md b/.changeset/silly-plums-ask.md new file mode 100644 index 00000000..34fead2f --- /dev/null +++ b/.changeset/silly-plums-ask.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Hokey: Deleted component in favor of using Badge diff --git a/.changeset/silver-ants-cheer.md b/.changeset/silver-ants-cheer.md new file mode 100644 index 00000000..0d5a70d0 --- /dev/null +++ b/.changeset/silver-ants-cheer.md @@ -0,0 +1,5 @@ +--- +"@reshaped/theming": major +--- + +Removed rgb color variables diff --git a/.changeset/silver-meals-arrive.md b/.changeset/silver-meals-arrive.md new file mode 100644 index 00000000..5755ff37 --- /dev/null +++ b/.changeset/silver-meals-arrive.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Reshaped: Updated default theme to slate diff --git a/.changeset/six-pets-take.md b/.changeset/six-pets-take.md new file mode 100644 index 00000000..a543078a --- /dev/null +++ b/.changeset/six-pets-take.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Autocomplete, Checkbox, HiddenInput, Radio, Select, Switch, TextArea, TextField: Dropped onFocus and onBlur in favor of attributes diff --git a/.changeset/slick-symbols-love.md b/.changeset/slick-symbols-love.md new file mode 100644 index 00000000..0af31613 --- /dev/null +++ b/.changeset/slick-symbols-love.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Pagination: Updated button variants used for selected state diff --git a/.changeset/slow-carpets-behave.md b/.changeset/slow-carpets-behave.md new file mode 100644 index 00000000..6e2227bf --- /dev/null +++ b/.changeset/slow-carpets-behave.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Card: Updated styling to use new shadow and border mixins diff --git a/.changeset/some-planes-kick.md b/.changeset/some-planes-kick.md index ec39f5d9..7326c2a9 100644 --- a/.changeset/some-planes-kick.md +++ b/.changeset/some-planes-kick.md @@ -1,5 +1,4 @@ --- -"@reshaped/headless": minor "reshaped": minor --- diff --git a/.changeset/sour-lies-look.md b/.changeset/sour-lies-look.md new file mode 100644 index 00000000..f43dc6b7 --- /dev/null +++ b/.changeset/sour-lies-look.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Calendar: Improved styles and fixed hover events / selection states diff --git a/.changeset/spicy-poets-make.md b/.changeset/spicy-poets-make.md new file mode 100644 index 00000000..2b9b4971 --- /dev/null +++ b/.changeset/spicy-poets-make.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Flyout, Popover: added scrollable element with its own className and attributes, padding moved to it insted of .inner diff --git a/.changeset/sunny-words-say.md b/.changeset/sunny-words-say.md new file mode 100644 index 00000000..dacc6dc7 --- /dev/null +++ b/.changeset/sunny-words-say.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Badge: Removed outline variant, update icon sizes/gaps/paddings diff --git a/.changeset/tall-chefs-report.md b/.changeset/tall-chefs-report.md new file mode 100644 index 00000000..25d23c17 --- /dev/null +++ b/.changeset/tall-chefs-report.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +ProgressBar: renamed Progress to ProgressBar diff --git a/.changeset/tall-schools-kick.md b/.changeset/tall-schools-kick.md new file mode 100644 index 00000000..0e435ea8 --- /dev/null +++ b/.changeset/tall-schools-kick.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +TextField: Updated default endSlotPadding to 2, layout improvements diff --git a/.changeset/tangy-papers-occur.md b/.changeset/tangy-papers-occur.md new file mode 100644 index 00000000..29772b19 --- /dev/null +++ b/.changeset/tangy-papers-occur.md @@ -0,0 +1,12 @@ +--- +"@reshaped/theming": major +--- + +Updated theming structure and values + +- Added x0-5 and x1-5 units +- Updated medium radius to 6px by default +- Added new shadow tokens for simulating borders mixed with shadows: `border`, `borderFaded`, `borderRaised`, `borderFadedRaised`, `borderOverlay`, `borderFadedOverlay` +- Updated typography structure. Title 1-6 moved to headline 1-3 for marketing use cases, featured 1-3 are now title 1-6. Body 2 and body 3 are now body 1 and body 2 with body 2 being the default body text style. +- Updated easing and duration values +- Added new backgroundHighlightedFaded color tokens for custom hover effects diff --git a/.changeset/tasty-animals-drive.md b/.changeset/tasty-animals-drive.md new file mode 100644 index 00000000..a28afe96 --- /dev/null +++ b/.changeset/tasty-animals-drive.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Button: Renamed elevated to raised diff --git a/.changeset/thirty-socks-rule.md b/.changeset/thirty-socks-rule.md new file mode 100644 index 00000000..b625ed84 --- /dev/null +++ b/.changeset/thirty-socks-rule.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +View: Updated shadow prop to use new shadow tokens diff --git a/.changeset/three-dingos-chew.md b/.changeset/three-dingos-chew.md new file mode 100644 index 00000000..11c1dc13 --- /dev/null +++ b/.changeset/three-dingos-chew.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Modal: Optimized swipe behavior diff --git a/.changeset/twelve-moles-check.md b/.changeset/twelve-moles-check.md new file mode 100644 index 00000000..559761b2 --- /dev/null +++ b/.changeset/twelve-moles-check.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Text: Updated the variant names to match the new typography tokens diff --git a/.changeset/twenty-chicken-lead.md b/.changeset/twenty-chicken-lead.md new file mode 100644 index 00000000..8155aca7 --- /dev/null +++ b/.changeset/twenty-chicken-lead.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Actionable: Moved focus state from using box-shadow to outline diff --git a/.changeset/violet-rings-dig.md b/.changeset/violet-rings-dig.md new file mode 100644 index 00000000..74f13c1b --- /dev/null +++ b/.changeset/violet-rings-dig.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Tabs: Removed automatic defaultValue detection in favor of manually passing it diff --git a/.changeset/warm-dingos-accept.md b/.changeset/warm-dingos-accept.md new file mode 100644 index 00000000..76b38f75 --- /dev/null +++ b/.changeset/warm-dingos-accept.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +View: aspectRatio no longer overrides children ratio and only changes the dimensions of the view itself diff --git a/.changeset/warm-tigers-wait.md b/.changeset/warm-tigers-wait.md new file mode 100644 index 00000000..03f31c8d --- /dev/null +++ b/.changeset/warm-tigers-wait.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Skeleton: Updated background color diff --git a/.changeset/wise-lands-cheer.md b/.changeset/wise-lands-cheer.md new file mode 100644 index 00000000..cc10ff99 --- /dev/null +++ b/.changeset/wise-lands-cheer.md @@ -0,0 +1,5 @@ +--- +"reshaped": major +--- + +Resizable: Added support for wrapping items and handles into re-usable components diff --git a/.changeset/young-meals-clap.md b/.changeset/young-meals-clap.md new file mode 100644 index 00000000..9bf7d520 --- /dev/null +++ b/.changeset/young-meals-clap.md @@ -0,0 +1,5 @@ +--- +"reshaped": patch +--- + +Avatar: Added outline to faded variant diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8fe777ab..7e5026ea 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,15 @@ ## Summary + ## Related Issue + ## Screenshots / Recordings + ## Notes for Reviewers + diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 00000000..4babe622 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,61 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 100, + "useTabs": true, + "trailingComma": "es5", + "sortImports": { + "customGroups": [ + { + "groupName": "internal-components", + "elementNamePattern": ["@/components/**"], + "selector": "internal" + }, + { + "groupName": "internal-hooks", + "elementNamePattern": ["@/hooks/**"], + "selector": "internal" + }, + { + "groupName": "internal-utilities", + "elementNamePattern": ["@/utilities/**"], + "selector": "internal" + }, + { + "groupName": "monorepo-value", + "elementNamePattern": ["@reshaped/**", "reshaped"], + "modifiers": ["value"], + "selector": "external" + }, + { + "groupName": "monorepo-type", + "elementNamePattern": ["@reshaped/**", "reshaped"], + "modifiers": ["type"], + "selector": "external" + } + ], + "groups": [ + ["value-builtin", "type-builtin"], + { "newlinesBetween": false }, + ["value-external", "type-external"], + { "newlinesBetween": false }, + ["monorepo-value", "monorepo-type"], + "internal-components", + { "newlinesBetween": false }, + "internal-hooks", + { "newlinesBetween": false }, + "internal-utilities", + { "newlinesBetween": false }, + ["value-internal", "type-internal"], + { "newlinesBetween": false }, + ["value-parent", "type-parent"], + { "newlinesBetween": false }, + ["value-sibling", "type-sibling"], + { "newlinesBetween": false }, + ["value-index", "type-index", "side_effect_style", "style"], + { "newlinesBetween": false }, + "unknown" + ] + }, + "sortPackageJson": false, + "ignorePatterns": ["src/themes/**", "packages/**/src/themes/**"] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 00000000..bad98c06 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,14 @@ +{ + "plugins": ["react", "jsx-a11y"], + "rules": { + "react/rules-of-hooks": "error" + }, + "overrides": [ + { + "files": ["**/*.stories.*", "**/*.story.*"], + "rules": { + "react/rules-of-hooks": "off" + } + } + ] +} diff --git a/.storybook/main.ts b/.storybook/main.ts index dfbc246a..7ca4b1db 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,20 +1,14 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; - +import type { StorybookConfig } from "@storybook/react-vite"; import { mergeConfig, UserConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; -import type { StorybookConfig } from "@storybook/react-vite"; - const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const config: StorybookConfig = { framework: "@storybook/react-vite", - features: { - experimentalComponentsManifest: true, - experimentalCodeExamples: true, - }, typescript: { reactDocgen: "react-docgen-typescript", reactDocgenTypescriptOptions: { @@ -26,10 +20,7 @@ const config: StorybookConfig = { propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), }, }, - stories: [ - "../packages/reshaped/src/**/*.stories.tsx", - "../packages/headless/src/**/*.stories.tsx", - ], + stories: ["../packages/reshaped/src/**/*.stories.tsx"], staticDirs: ["./public"], addons: [ "@storybook/addon-vitest", @@ -47,17 +38,9 @@ const config: StorybookConfig = { ], async viteFinal(config: UserConfig) { return mergeConfig(config, { - resolve: { - alias: { - "@reshaped/headless": resolve(__dirname, "../packages/headless/src"), - }, - }, plugins: [ tsconfigPaths({ - projects: [ - resolve(__dirname, "../packages/reshaped/tsconfig.json"), - resolve(__dirname, "../packages/headless/tsconfig.json"), - ], + projects: [resolve(__dirname, "../packages/reshaped/tsconfig.json")], }), ], css: { diff --git a/.storybook/postcss.config.mjs b/.storybook/postcss.config.mjs index c3566daf..eba776ee 100644 --- a/.storybook/postcss.config.mjs +++ b/.storybook/postcss.config.mjs @@ -1,6 +1,5 @@ import path from "path"; import { fileURLToPath } from "url"; - import postcssGlobalData from "@csstools/postcss-global-data"; import customMediaPlugin from "postcss-custom-media"; @@ -13,7 +12,7 @@ export default { plugins: [ ...baseConfig.plugins, postcssGlobalData({ - files: [path.resolve(__dirname, "../packages/reshaped/src/themes/reshaped/media.css")], + files: [path.resolve(__dirname, "../packages/reshaped/src/themes/slate/media.css")], }), customMediaPlugin(), ], diff --git a/.storybook/preview.jsx b/.storybook/preview.jsx index bba0fc33..6d600cbf 100644 --- a/.storybook/preview.jsx +++ b/.storybook/preview.jsx @@ -1,15 +1,15 @@ import React from "react"; -import { useRTL } from "../packages/headless"; -import Reshaped from "../packages/reshaped/src/components/Reshaped"; + import Button from "../packages/reshaped/src/components/Button"; -import View from "../packages/reshaped/src/components/View"; -import Text from "../packages/reshaped/src/components/Text"; -import Hidden from "../packages/reshaped/src/components/Hidden"; import DropdownMenu from "../packages/reshaped/src/components/DropdownMenu"; +import Hidden from "../packages/reshaped/src/components/Hidden"; import Icon from "../packages/reshaped/src/components/Icon"; +import Reshaped from "../packages/reshaped/src/components/Reshaped"; +import Text from "../packages/reshaped/src/components/Text"; import { useTheme } from "../packages/reshaped/src/components/Theme"; +import View from "../packages/reshaped/src/components/View"; +import useRTL from "../packages/reshaped/src/hooks/useRTL"; import IconCheckmark from "../packages/reshaped/src/icons/Checkmark"; -import "../packages/reshaped/src/themes/reshaped/theme.css"; import "../packages/reshaped/src/themes/slate/theme.css"; import "../packages/reshaped/src/themes/figma/theme.css"; import "../packages/reshaped/src/themes/fragments/twitter/theme.css"; @@ -61,7 +61,7 @@ const ThemeSwitch = () => { Toggle mode - + {(attributes) => ( - - - ), -}; - -export const positionRelative = { - name: "position, positionType: relative", - render: () => ( - - - - - - - - - - - - - - ), -}; - -export const positionAbsolute = { - name: "position, positionType: absolute", - render: () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ), -}; - -export const positionFixed = { - name: "position, positionType: fixed", - render: () => ( - <> - - - - - - - - - - - - - - - - - - - - - - - - -
- - ), -}; - -export const elevated = { - name: "elevated", - render: () => ( - - - - - - - - - - - - - - - - - - - - - - ), -}; - -export const offset = { - name: "offset", - render: () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - ), -}; - -export const active = { - name: "active", - render: () => { - const barToggle = useToggle(); - - return ( - <> - - - - - - ); - }, -}; - -export const padding = { - name: "padding, paddingBlock, paddingInline", - render: () => ( - - - - - - - - - - - - - - - - - - - - ), -}; - -export const className: StoryObj = { - name: "className, attributes", - render: () => ( -
- - - -
- ), - play: async ({ canvas }) => { - const root = canvas.getByTestId("root").firstChild; - - expect(root).toHaveClass("test-classname"); - expect(root).toHaveAttribute("id", "test-id"); - }, -}; diff --git a/packages/reshaped/src/components/Actionable/Actionable.module.css b/packages/reshaped/src/components/Actionable/Actionable.module.css index ad116764..f4d8ba51 100644 --- a/packages/reshaped/src/components/Actionable/Actionable.module.css +++ b/packages/reshaped/src/components/Actionable/Actionable.module.css @@ -36,30 +36,31 @@ [data-rs-keyboard] .root { &:focus { - outline: none; + outline: var(--rs-outline); + outline-offset: var(--rs-outline-width); z-index: var(--rs-z-index-relative); - box-shadow: var(--rs-shadow-focus); } &.--inset:focus { - box-shadow: var(--rs-shadow-focus-inset); + outline-offset: calc(var(--rs-outline-width) * -1); } &.--disabled-focus-ring:focus { - box-shadow: none; + outline: none; } &.--radius-inherit { &:focus { - box-shadow: none; + outline: none; } &:focus > * { - box-shadow: var(--rs-shadow-focus); + outline: var(--rs-outline); + outline-offset: var(--rs-outline-width); } &.--inset:focus > * { - box-shadow: var(--rs-shadow-focus-inset); + outline-offset: calc(var(--rs-outline-width) * -1); } } } @@ -78,7 +79,7 @@ button.root, .root.--disabled, .root[disabled] { - cursor: not-allowed; + cursor: default; &:active { transform: none; diff --git a/packages/reshaped/src/components/Actionable/Actionable.tsx b/packages/reshaped/src/components/Actionable/Actionable.tsx index 7c8fe4d7..577a441a 100644 --- a/packages/reshaped/src/components/Actionable/Actionable.tsx +++ b/packages/reshaped/src/components/Actionable/Actionable.tsx @@ -1,31 +1,79 @@ "use client"; -import { - Actionable as UnstyledActionable, - type ActionableRef as UnstyledActionableRef, -} from "@reshaped/headless"; -import { forwardRef } from "react"; - -import s from "./Actionable.module.css"; +import React, { forwardRef } from "react"; +import { classNames, keys } from "@reshaped/utilities"; import type * as T from "./Actionable.types"; +import s from "./Actionable.module.css"; -const Actionable = forwardRef((props, ref) => { +const Actionable = forwardRef((props, ref) => { const { - // Styled props + children, + render, + href, + onClick, + type, + disabled, + as, + stopPropagation, + className, + attributes, insetFocus, disableFocusRing, borderRadius, fullWidth, touchHitbox, + } = props; - // Unstyled props used for internal behavior - children, - className, - disabled, + const rootAttributes: T.Props["attributes"] = { ...attributes }; + const hasClickHandler = onClick || (attributes?.onClick as T.Props["onClick"]); + const hasFocusHandler = attributes?.onFocus || attributes?.onBlur; + const isLink = Boolean(href || attributes?.href); + // Including attributes ref for the cases when event listeners are added through it + // To make sure it doesn't render a span + const isButton = Boolean(hasClickHandler || hasFocusHandler || type || attributes?.ref); + const renderedAsButton = !isLink && isButton && (!as || as === "button"); + // Using any here to let TS save on type resolving, otherwise TS throws an error due to the type complexity + let TagName: any; + + if (isLink) { + TagName = "a"; + rootAttributes.href = disabled ? undefined : href || attributes?.href; + } else if (renderedAsButton) { + TagName = "button"; + rootAttributes.type = type || attributes?.type || "button"; + rootAttributes.disabled = disabled || attributes?.disabled; + } else if (isButton) { + const isFocusable = as === "label"; + const simulateButton = !isFocusable || hasClickHandler || hasFocusHandler; + + TagName = as || "span"; + rootAttributes.role = simulateButton ? "button" : undefined; + rootAttributes.tabIndex = simulateButton ? 0 : undefined; + } else { + TagName = as || "span"; + } + + const handlePress: T.Props["onClick"] = (event) => { + if (disabled) return; + if (stopPropagation) event.stopPropagation(); + + onClick?.(event); + attributes?.onClick?.(event as React.MouseEvent); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + const isSpace = event.key === keys.SPACE; + const isEnter = event.key === keys.ENTER; + + if (!isSpace && !isEnter) return; + if (rootAttributes.role !== "button") return; + + if (stopPropagation) event.stopPropagation(); + event.preventDefault(); + handlePress(event); + }; - ...unstyledProps - } = props; const rootClassNames = [ s.root, className, @@ -36,12 +84,23 @@ const Actionable = forwardRef((props, ref) => { fullWidth && s["--full-width"], ]; - return ( - - {touchHitbox && !disabled && } - {children} - - ); + const tagAttributes = { + ref: ref as T.AttributesRef, + ...rootAttributes, + className: classNames(rootClassNames), + onClick: handlePress, + onKeyDown: handleKeyDown, + "aria-disabled": disabled ? true : undefined, + children: ( + <> + {touchHitbox && !disabled && } + {children} + + ), + }; + + if (render) return render(tagAttributes as T.RenderAttributes); + return ; }); Actionable.displayName = "Actionable"; diff --git a/packages/reshaped/src/components/Actionable/Actionable.types.ts b/packages/reshaped/src/components/Actionable/Actionable.types.ts index 52a15ba6..9d380b71 100644 --- a/packages/reshaped/src/components/Actionable/Actionable.types.ts +++ b/packages/reshaped/src/components/Actionable/Actionable.types.ts @@ -1,6 +1,40 @@ -import type { ActionableProps as UnstyledActionableProps } from "@reshaped/headless"; +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; -export type Props = UnstyledActionableProps & { +import type { Attributes as AttributesType } from "@/types/global"; + +export type AttributesRef = React.RefObject; +type Attributes = AttributesType<"button"> & + Omit> & { + ref?: AttributesRef; + }; + +export type RenderAttributes = Omit & { + ref: React.Ref & React.Ref; + children: React.ReactNode; +}; + +export type Props = { + /** Node for inserting the content */ + children?: React.ReactNode; + /** Render a custom root element, useful for integrating with routers */ + render?: (attributes: RenderAttributes) => React.ReactNode; + /** Callback when clicked, renders it as a button tag if href is not provided */ + onClick?: (e: React.MouseEvent | React.KeyboardEvent) => void; + /** URL, renders it as an anchor tag */ + href?: string; + /** Type attribute, renders it as a button tag */ + type?: React.ButtonHTMLAttributes["type"]; + /** Disable from user interaction */ + disabled?: boolean; + /** Prevent the event from bubbling up to the parent */ + stopPropagation?: boolean; + /** Render as a different element */ + as?: keyof React.JSX.IntrinsicElements; + /** Additional classname for the root element */ + className?: ClassName; + /** Additional attributes for the root element */ + attributes?: Attributes; /** Enable a minimum required touch hitbox */ touchHitbox?: boolean; /** Take up the full width of its parent */ @@ -12,3 +46,5 @@ export type Props = UnstyledActionableProps & { /** Apply the focus ring to the child and rely on its border radius */ borderRadius?: "inherit"; }; + +export type Ref = HTMLButtonElement | HTMLAnchorElement; diff --git a/packages/reshaped/src/components/Actionable/index.ts b/packages/reshaped/src/components/Actionable/index.ts index 4e2973eb..9b9cbb24 100644 --- a/packages/reshaped/src/components/Actionable/index.ts +++ b/packages/reshaped/src/components/Actionable/index.ts @@ -1,4 +1,2 @@ export { default } from "./Actionable"; -export type { Props as ActionableProps } from "./Actionable.types"; - -export type { ActionableRef } from "@reshaped/headless"; +export type { Props as ActionableProps, Ref as ActionableRef } from "./Actionable.types"; diff --git a/packages/reshaped/src/components/Actionable/tests/Actionable.stories.tsx b/packages/reshaped/src/components/Actionable/tests/Actionable.stories.tsx index 5028e42b..e555aaa2 100644 --- a/packages/reshaped/src/components/Actionable/tests/Actionable.stories.tsx +++ b/packages/reshaped/src/components/Actionable/tests/Actionable.stories.tsx @@ -1,7 +1,8 @@ import { StoryObj } from "@storybook/react-vite"; -import { userEvent, expect, fn } from "storybook/test"; +import { expect, fn, userEvent } from "storybook/test"; import Actionable from "@/components/Actionable"; +import Button from "@/components/Button"; import View from "@/components/View"; import { Example } from "@/utilities/storybook"; @@ -228,6 +229,19 @@ export const as: StoryObj = { Trigger + + {}} + render={({ children, ref, disabled, ...attributes }) => ( + + )} + > + Trigger + + ), play: ({ canvas }) => { @@ -245,7 +259,7 @@ export const stopPropagation: StoryObj<{ handleParentClick: ReturnType ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions + // oxlint-disable-next-line jsx_a11y/click-events-have-key-events, jsx_a11y/no-static-element-interactions
{}}> Trigger diff --git a/packages/reshaped/src/components/Alert/Alert.module.css b/packages/reshaped/src/components/Alert/Alert.module.css index 56f47411..076f284d 100644 --- a/packages/reshaped/src/components/Alert/Alert.module.css +++ b/packages/reshaped/src/components/Alert/Alert.module.css @@ -1,6 +1,6 @@ .icon { /* Align the icon vertically with the text for rem fonts */ - height: var(--rs-line-height-body-3); + height: var(--rs-line-height-body-2); display: flex; align-items: center; } diff --git a/packages/reshaped/src/components/Alert/Alert.tsx b/packages/reshaped/src/components/Alert/Alert.tsx index 2bf3ebd5..01afbd82 100644 --- a/packages/reshaped/src/components/Alert/Alert.tsx +++ b/packages/reshaped/src/components/Alert/Alert.tsx @@ -3,9 +3,8 @@ import React from "react"; import Icon from "@/components/Icon"; import Text from "@/components/Text"; import View from "@/components/View"; - -import s from "./Alert.module.css"; import * as T from "./Alert.types"; +import s from "./Alert.module.css"; const Alert: React.FC = (props) => { const { @@ -19,20 +18,19 @@ const Alert: React.FC = (props) => { className, attributes, } = props; - const isNeutral = color === "neutral"; const renderContent = () => { if (inline) { return ( <> {title && ( - + {title} )} {title && children && " "} {children && ( - + {children} )} @@ -43,11 +41,11 @@ const Alert: React.FC = (props) => { return ( {title && ( - + {title} )} - {children && {children}} + {children && {children}} ); }; @@ -59,7 +57,7 @@ const Alert: React.FC = (props) => { {inline ? {content} : content} {actionsSlot && ( - + {actionsSlot} @@ -75,7 +73,7 @@ const Alert: React.FC = (props) => { gap={3} padding={4} bleed={bleed} - borderRadius="medium" + borderRadius="large" borderColor={`${color}-faded`} backgroundColor={`${color}-faded`} className={className} @@ -87,7 +85,7 @@ const Alert: React.FC = (props) => { {icon ? ( <>
- +
{applyActions(renderContent())} diff --git a/packages/reshaped/src/components/Alert/Alert.types.ts b/packages/reshaped/src/components/Alert/Alert.types.ts index 93c73ba2..2889e3ea 100644 --- a/packages/reshaped/src/components/Alert/Alert.types.ts +++ b/packages/reshaped/src/components/Alert/Alert.types.ts @@ -1,7 +1,9 @@ +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type { IconProps } from "@/components/Icon"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; export type Props = { /** SVG component for the icon */ diff --git a/packages/reshaped/src/components/Alert/tests/Alert.stories.tsx b/packages/reshaped/src/components/Alert/tests/Alert.stories.tsx index 3ebdf6e1..b60e38e3 100644 --- a/packages/reshaped/src/components/Alert/tests/Alert.stories.tsx +++ b/packages/reshaped/src/components/Alert/tests/Alert.stories.tsx @@ -3,8 +3,8 @@ import { expect } from "storybook/test"; import Alert from "@/components/Alert"; import Link from "@/components/Link"; -import IconZap from "@/icons/Zap"; import { Example, Placeholder } from "@/utilities/storybook"; +import IconZap from "@/icons/Zap"; export default { title: "Components/Alert", diff --git a/packages/reshaped/src/components/Autocomplete/Autocomplete.tsx b/packages/reshaped/src/components/Autocomplete/Autocomplete.tsx index 34396c4f..bc287d17 100644 --- a/packages/reshaped/src/components/Autocomplete/Autocomplete.tsx +++ b/packages/reshaped/src/components/Autocomplete/Autocomplete.tsx @@ -1,22 +1,18 @@ "use client"; -import { - useIsomorphicLayoutEffect, - useHotkeys, - useHandlerRef, - useElementId, -} from "@reshaped/headless"; import React from "react"; import DropdownMenu from "@/components/DropdownMenu"; +import type { MenuItemProps } from "@/components/MenuItem"; import TextField from "@/components/TextField"; +import type { TextFieldProps } from "@/components/TextField"; +import useElementId from "@/hooks/useElementId"; +import useHandlerRef from "@/hooks/useHandlerRef"; +import useHotkeys from "@/hooks/useHotkeys"; +import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect"; import * as keys from "@/constants/keys"; - -import s from "./Autocomplete.module.css"; import * as T from "./Autocomplete.types"; - -import type { MenuItemProps } from "@/components/MenuItem"; -import type { TextFieldProps } from "@/components/TextField"; +import s from "./Autocomplete.module.css"; const AutocompleteContext = React.createContext({} as T.Context); @@ -35,7 +31,6 @@ const Autocomplete: React.FC = (props) => { onOpen, onClose, fallbackAdjustLayout, - fallbackMinWidth, fallbackMinHeight, contentMaxHeight, contentZIndex, @@ -205,7 +200,6 @@ const Autocomplete: React.FC = (props) => { onOpen={handleOpen} containerRef={containerRef} fallbackAdjustLayout={fallbackAdjustLayout} - fallbackMinWidth={fallbackMinWidth} fallbackMinHeight={fallbackMinHeight} contentMaxHeight={contentMaxHeight} contentZIndex={contentZIndex} @@ -222,16 +216,14 @@ const Autocomplete: React.FC = (props) => { attributes={{ ...textFieldProps.attributes, // Ignoring the type check since TS can't infer the correct html element type - // eslint-disable-next-line @typescript-eslint/no-explicit-any ref: ref as any, onClick: attributes.onFocus, }} inputAttributes={{ ...textFieldProps.inputAttributes, ...attributes, - onFocus: (e) => { + onFocus: () => { attributes.onFocus?.(); - textFieldProps.onFocus?.(e); // Only select the value when user clicks on the input if (!lockedRef.current) inputRef.current?.select(); }, diff --git a/packages/reshaped/src/components/Autocomplete/Autocomplete.types.ts b/packages/reshaped/src/components/Autocomplete/Autocomplete.types.ts index 8b68cfc9..a4f24750 100644 --- a/packages/reshaped/src/components/Autocomplete/Autocomplete.types.ts +++ b/packages/reshaped/src/components/Autocomplete/Autocomplete.types.ts @@ -1,4 +1,4 @@ -import type { DropdownMenuProps, DropdownMenuInstance } from "@/components/DropdownMenu"; +import type { DropdownMenuInstance, DropdownMenuProps } from "@/components/DropdownMenu"; import type { MenuItemProps } from "@/components/MenuItem"; import type { TextFieldProps } from "@/components/TextField"; import type * as G from "@/types/global"; @@ -19,7 +19,6 @@ export type Props = TextFieldProps & | "onOpen" | "onClose" | "fallbackAdjustLayout" - | "fallbackMinWidth" | "fallbackMinHeight" | "contentMaxHeight" | "contentZIndex" diff --git a/packages/reshaped/src/components/Autocomplete/index.ts b/packages/reshaped/src/components/Autocomplete/index.ts index ecf417df..cdad7df2 100644 --- a/packages/reshaped/src/components/Autocomplete/index.ts +++ b/packages/reshaped/src/components/Autocomplete/index.ts @@ -8,6 +8,6 @@ AutocompleteRoot.Item = AutocompleteItem; export default AutocompleteRoot; export type { - Props as AutocompleteProps, Instance as AutocompleteInstance, + Props as AutocompleteProps, } from "./Autocomplete.types"; diff --git a/packages/reshaped/src/components/Autocomplete/tests/Autocomplete.stories.tsx b/packages/reshaped/src/components/Autocomplete/tests/Autocomplete.stories.tsx index 6519c630..57f3d739 100644 --- a/packages/reshaped/src/components/Autocomplete/tests/Autocomplete.stories.tsx +++ b/packages/reshaped/src/components/Autocomplete/tests/Autocomplete.stories.tsx @@ -1,11 +1,11 @@ -import { useToggle } from "@reshaped/headless"; import { StoryObj } from "@storybook/react-vite"; import React from "react"; -import { fn, expect, Mock, within, waitFor, userEvent, fireEvent } from "storybook/test"; +import { expect, fireEvent, fn, Mock, userEvent, waitFor, within } from "storybook/test"; import Autocomplete from "@/components/Autocomplete"; import Badge from "@/components/Badge"; import FormControl from "@/components/FormControl"; +import useToggle from "@/hooks/useToggle"; import { sleep } from "@/utilities/helpers"; import { Example } from "@/utilities/storybook"; diff --git a/packages/reshaped/src/components/Avatar/Avatar.tsx b/packages/reshaped/src/components/Avatar/Avatar.tsx index e8961c96..e8a1b023 100644 --- a/packages/reshaped/src/components/Avatar/Avatar.tsx +++ b/packages/reshaped/src/components/Avatar/Avatar.tsx @@ -1,14 +1,12 @@ -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; import Icon from "@/components/Icon"; import Image, { type ImageProps } from "@/components/Image"; import View from "@/components/View"; -import { resolveMixin } from "@/styles/mixin"; import { responsivePropDependency } from "@/utilities/props"; - -import s from "./Avatar.module.css"; - +import { resolveMixin } from "@/styles/mixin"; import type * as T from "./Avatar.types"; +import s from "./Avatar.module.css"; const Avatar: React.FC = (props) => { const { @@ -27,8 +25,8 @@ const Avatar: React.FC = (props) => { const alt = props.alt || imageAttributes?.alt; const radius = squared ? responsivePropDependency(size, (size) => { - if (size >= 24) return "large"; - if (size >= 12) return "medium"; + if (size >= 20) return "large"; + if (size >= 9) return "medium"; return "small"; }) : "circular"; @@ -64,6 +62,7 @@ const Avatar: React.FC = (props) => { borderRadius={radius} attributes={{ ...attributes, style: { ...mixinStyles?.variables } }} backgroundColor={variant === "faded" ? `${color}-${variant}` : color} + borderColor={variant === "faded" ? `${color}-faded` : undefined} className={rootClassNames} > {icon ? ( diff --git a/packages/reshaped/src/components/Avatar/Avatar.types.ts b/packages/reshaped/src/components/Avatar/Avatar.types.ts index 2f89077e..980b6a6d 100644 --- a/packages/reshaped/src/components/Avatar/Avatar.types.ts +++ b/packages/reshaped/src/components/Avatar/Avatar.types.ts @@ -1,6 +1,8 @@ +import type { ClassName } from "@reshaped/utilities"; + import type { IconProps } from "@/components/Icon"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; +import type { Attributes } from "@/types/global"; export type Props = { /** Image URL */ diff --git a/packages/reshaped/src/components/Avatar/tests/Avatar.stories.tsx b/packages/reshaped/src/components/Avatar/tests/Avatar.stories.tsx index e742c01a..dbcc88ac 100644 --- a/packages/reshaped/src/components/Avatar/tests/Avatar.stories.tsx +++ b/packages/reshaped/src/components/Avatar/tests/Avatar.stories.tsx @@ -4,8 +4,8 @@ import { expect, fn, Mock, waitFor } from "storybook/test"; import Avatar from "@/components/Avatar"; import View from "@/components/View"; -import IconZap from "@/icons/Zap"; import { Example } from "@/utilities/storybook"; +import IconZap from "@/icons/Zap"; export default { title: "Components/Avatar", @@ -188,7 +188,7 @@ export const renderImage: StoryObj = { } /> diff --git a/packages/reshaped/src/components/Badge/Badge.module.css b/packages/reshaped/src/components/Badge/Badge.module.css index c9ba480d..7e7a29d2 100644 --- a/packages/reshaped/src/components/Badge/Badge.module.css +++ b/packages/reshaped/src/components/Badge/Badge.module.css @@ -30,6 +30,14 @@ } .icon { + &:first-child { + margin-inline-start: calc(var(--rs-unit-x0-5) * -1); + } + + &:last-child { + margin-inline-end: calc(var(--rs-unit-x0-5) * -1); + } + &:only-child { margin-inline: calc(var(--rs-unit-x1) * -1); } @@ -39,6 +47,10 @@ border-radius: var(--rs-radius-small); transition: var(--rs-duration-fast) var(--rs-easing-standard); transition-property: opacity; + + &:last-child { + margin-inline-end: calc(var(--rs-unit-x0-5) * -1); + } } .root.--highlighted { @@ -52,35 +64,22 @@ } } -.root.--variant { - &-faded { - background: var(--rs-color-background-neutral-faded); - color: var(--rs-color-foreground-neutral-faded); - } - - &-outline { - --rs-badge-border-color: var(--rs-color-border-neutral); +.root.--variant-faded { + --rs-badge-border-color: var(--rs-color-border-neutral-faded); - background: none; - } + background: var(--rs-color-background-neutral-faded); + color: var(--rs-color-foreground-neutral-faded); } .root.--color-positive { background: var(--rs-color-background-positive); color: var(--rs-color-on-background-positive); - &.--variant { - &-faded { - background: var(--rs-color-background-positive-faded); - color: var(--rs-color-foreground-positive); - } - - &-outline { - --rs-badge-border-color: var(--rs-color-border-positive-faded); + &.--variant-faded { + --rs-badge-border-color: var(--rs-color-border-positive-faded); - background: none; - color: var(--rs-color-foreground-positive); - } + background: var(--rs-color-background-positive-faded); + color: var(--rs-color-foreground-positive); } } @@ -88,18 +87,11 @@ background: var(--rs-color-background-critical); color: var(--rs-color-on-background-critical); - &.--variant { - &-faded { - background: var(--rs-color-background-critical-faded); - color: var(--rs-color-foreground-critical); - } - - &-outline { - --rs-badge-border-color: var(--rs-color-border-critical-faded); + &.--variant-faded { + --rs-badge-border-color: var(--rs-color-border-critical-faded); - background: none; - color: var(--rs-color-foreground-critical); - } + background: var(--rs-color-background-critical-faded); + color: var(--rs-color-foreground-critical); } } @@ -107,18 +99,11 @@ background: var(--rs-color-background-warning); color: var(--rs-color-on-background-warning); - &.--variant { - &-faded { - background: var(--rs-color-background-warning-faded); - color: var(--rs-color-foreground-warning); - } - - &-outline { - --rs-badge-border-color: var(--rs-color-border-warning-faded); + &.--variant-faded { + --rs-badge-border-color: var(--rs-color-border-warning-faded); - background: none; - color: var(--rs-color-foreground-warning); - } + background: var(--rs-color-background-warning-faded); + color: var(--rs-color-foreground-warning); } } @@ -126,28 +111,21 @@ background: var(--rs-color-background-primary); color: var(--rs-color-on-background-primary); - &.--variant { - &-faded { - background: var(--rs-color-background-primary-faded); - color: var(--rs-color-foreground-primary); - } - - &-outline { - --rs-badge-border-color: var(--rs-color-border-primary-faded); + &.--variant-faded { + --rs-badge-border-color: var(--rs-color-border-primary-faded); - background: none; - color: var(--rs-color-foreground-primary); - } + background: var(--rs-color-background-primary-faded); + color: var(--rs-color-foreground-primary); } } .root.--size { &-small { --rs-badge-p-v: calc(var(--rs-unit-x1) / 2); - --rs-badge-p-h: var(--rs-unit-x1); + --rs-badge-p-h: var(--rs-unit-x1-5); --rs-badge-line-height: var(--rs-line-height-caption-1); --rs-badge-empty-size: var(--rs-unit-x2); - --rs-badge-gap: calc(var(--rs-unit-x1) / 2); + --rs-badge-gap: calc(var(--rs-unit-x1)); } &-medium { @@ -160,10 +138,10 @@ &-large { --rs-badge-p-v: var(--rs-unit-x1); - --rs-badge-p-h: var(--rs-unit-x2); - --rs-badge-line-height: var(--rs-line-height-body-3); + --rs-badge-p-h: calc(var(--rs-unit-x0-5) * 5); + --rs-badge-line-height: var(--rs-line-height-body-2); --rs-badge-empty-size: var(--rs-unit-x4); - --rs-badge-gap: var(--rs-unit-x1); + --rs-badge-gap: var(--rs-unit-x1-5); } } diff --git a/packages/reshaped/src/components/Badge/Badge.tsx b/packages/reshaped/src/components/Badge/Badge.tsx index 38b41e44..d9c8c285 100644 --- a/packages/reshaped/src/components/Badge/Badge.tsx +++ b/packages/reshaped/src/components/Badge/Badge.tsx @@ -1,14 +1,12 @@ -import { classNames } from "@reshaped/headless"; import { forwardRef } from "react"; +import { classNames } from "@reshaped/utilities"; import Actionable, { type ActionableProps, type ActionableRef } from "@/components/Actionable"; import Icon from "@/components/Icon"; import Text from "@/components/Text"; import IconClose from "@/icons/Close"; - -import s from "./Badge.module.css"; - import type * as T from "./Badge.types"; +import s from "./Badge.module.css"; const Badge = forwardRef((props, ref) => { const { @@ -30,7 +28,11 @@ const Badge = forwardRef((props, ref) => { as, } = props; const isActionable = !!(onClick || href); - const iconSize = size === "small" ? 3 : 4; + const iconSize = { + small: 3, + medium: 3.5, + large: 4, + }[size]; const hasText = children !== undefined && children !== null; const empty = !hasText && !icon && !endIcon; const rootClassName = classNames( @@ -64,7 +66,7 @@ const Badge = forwardRef((props, ref) => { {icon && } {hasText && (
+ + Badge + Badge diff --git a/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx b/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx index 80b68220..d28e775f 100644 --- a/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -1,7 +1,7 @@ "use client"; -import { classNames } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; import Button from "@/components/Button"; import Icon from "@/components/Icon"; @@ -9,7 +9,6 @@ import Text from "@/components/Text"; import View from "@/components/View"; import IconChevronRight from "@/icons/ChevronRight"; import IconDotsHorizontal from "@/icons/DotsHorizontal"; - import * as T from "./Breadcrumbs.types"; const Breadcrumbs: React.FC = (props) => { @@ -49,7 +48,6 @@ const Breadcrumbs: React.FC = (props) => { const isAfterCollapse = renderIndex > lastCollapsedIndex; const isDisplayed = !visibleItems || isBeforeCollapse || isAfterCollapse || expanded; const isCollapseButton = renderIndex === lastCollapsedIndex; - // eslint-disable-next-line react-hooks/immutability renderIndex += 1; let itemNode = null; @@ -81,7 +79,7 @@ const Breadcrumbs: React.FC = (props) => { {separator || }
)} - + {itemNode} diff --git a/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.types.ts b/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.types.ts index 6b39f362..b187a07b 100644 --- a/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.types.ts +++ b/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.types.ts @@ -1,6 +1,8 @@ -import type { LinkProps } from "@/components/Link"; -import type { Attributes, ClassName } from "@reshaped/headless"; import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + +import type { LinkProps } from "@/components/Link"; +import type { Attributes } from "@/types/global"; export type Props = { /** Node for inserting children to position items */ @@ -13,9 +15,8 @@ export type Props = { color?: "neutral" | "primary"; /** Number of items to show by default, others will be hidden and can be expanded */ defaultVisibleItems?: number; - // TODO: make required in the v4 /** Aria label for the expand button */ - expandAriaLabel?: string; + expandAriaLabel: string; /** Turn expand button into static text and disable the expand functionality */ disableExpand?: boolean; /** aria-label attribute for the component */ diff --git a/packages/reshaped/src/components/Breadcrumbs/BreadcrumbsItem.tsx b/packages/reshaped/src/components/Breadcrumbs/BreadcrumbsItem.tsx index 83106d0e..d51fa256 100644 --- a/packages/reshaped/src/components/Breadcrumbs/BreadcrumbsItem.tsx +++ b/packages/reshaped/src/components/Breadcrumbs/BreadcrumbsItem.tsx @@ -1,6 +1,5 @@ import Link from "@/components/Link"; import Text from "@/components/Text"; - import type * as T from "./Breadcrumbs.types"; const BreadcrumbsItem: React.FC = (props) => { @@ -8,7 +7,7 @@ const BreadcrumbsItem: React.FC = (props) => { if (!href && !onClick && !disabled) { return ( - + {children} ); diff --git a/packages/reshaped/src/components/Breadcrumbs/tests/Breadcrumbs.stories.tsx b/packages/reshaped/src/components/Breadcrumbs/tests/Breadcrumbs.stories.tsx index edd0b349..96dfddef 100644 --- a/packages/reshaped/src/components/Breadcrumbs/tests/Breadcrumbs.stories.tsx +++ b/packages/reshaped/src/components/Breadcrumbs/tests/Breadcrumbs.stories.tsx @@ -3,8 +3,8 @@ import { expect, fn, userEvent } from "storybook/test"; import Badge from "@/components/Badge"; import Breadcrumbs from "@/components/Breadcrumbs"; -import IconZap from "@/icons/Zap"; import { Example } from "@/utilities/storybook"; +import IconZap from "@/icons/Zap"; export default { title: "Components/Breadcrumbs", @@ -21,7 +21,7 @@ export const color = { render: () => ( - + {}}>Item 1 {}}>Item 2 Item 3 @@ -29,7 +29,7 @@ export const color = { - + {}}>Item 1 {}}>Item 2 Item 3 @@ -44,7 +44,7 @@ export const item = { render: () => ( - + {}}>Item 1 {}} disabled> Disabled item 2 @@ -61,7 +61,11 @@ export const icon = { render: () => ( - + {}}> Item 1 @@ -78,7 +82,12 @@ export const slots = { render: () => ( - + {}}>Item 1 {}}>Item 2 Item 3 @@ -86,7 +95,7 @@ export const slots = { - + {}}> Item 1 @@ -133,7 +142,12 @@ export const collapsed: StoryObj = { - + {}}>Item 1 {}}>Item 2 {}}>Item 3 @@ -166,7 +180,7 @@ export const multiline = { render: () => ( - + {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => ( {}} key={i}> Item {i} @@ -184,7 +198,7 @@ export const onClick: StoryObj<{ handleClick: ReturnType }> = { handleClick: fn(), }, render: (args) => ( - + Trigger Trigger @@ -208,7 +222,7 @@ export const onClick: StoryObj<{ handleClick: ReturnType }> = { export const href: StoryObj = { name: "item, href", render: () => ( - + Trigger ), @@ -223,7 +237,11 @@ export const className: StoryObj = { name: "className, attributes", render: () => (
- + Trigger
diff --git a/packages/reshaped/src/components/Button/Button.module.css b/packages/reshaped/src/components/Button/Button.module.css index 9cc1b1a4..83f38f9d 100644 --- a/packages/reshaped/src/components/Button/Button.module.css +++ b/packages/reshaped/src/components/Button/Button.module.css @@ -3,28 +3,25 @@ */ .root { - /* Using --rs-button-p and --rs-p to dynamically reassign --rs-p for all sizes of ghost buttons */ + /* Using --rs-button-p and --rs-p to dynamically reassign --rs-p for all sizes of ghost buttons, used by Aligner */ --rs-p-v: var(--rs-button-p-v); --rs-p-h: var(--rs-button-p-h); - --rs-button-highlight-opacity: 0; - --rs-button-highlight-opacity-base: 0; - --rs-button-border-color: transparent; - --rs-button-border-width: 0px; transition: var(--rs-duration-fast) var(--rs-easing-standard); - transition-property: background-color, box-shadow, border-color, color, transform; - padding: calc(var(--rs-unit-x1) - var(--rs-button-border-width)) - calc(var(--rs-p-h) - var(--rs-button-border-width)); + transition-property: background-color, box-shadow, border-color, color, transform, opacity; + padding: var(--rs-unit-x1) var(--rs-p-h); border-radius: var(--rs-button-radius); cursor: pointer; position: relative; z-index: 0; + + /* In case it breaks into multiple lines – it should keep default text alignment */ text-align: initial; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; - border: var(--rs-button-border-width) solid var(--rs-button-border-color); + user-select: none; -webkit-tap-highlight-color: transparent; font-family: var(--rs-font-family-body); font-weight: var(--rs-font-weight-medium); @@ -37,63 +34,64 @@ var(--rs-button-line-height) - (var(--rs-unit-x1) * 2) + (var(--rs-button-p-h) * 2) ); background-color: var(--rs-button-background-color); - color: var(--rs-button-foreground-color); + color: var(--rs-button-color); + isolation: isolate; - &::before { - content: ""; - position: absolute; - inset: 0; - transition: var(--rs-duration-fast) var(--rs-easing-standard); - transition-property: opacity, background-color; - opacity: var(--rs-button-highlight-opacity-base); - border-radius: var(--rs-button-radius); + &:active { + transform: scale(0.985) translateZ(0); + } - /* Use highlight based on the text color with an ability to override it */ - background: var(--rs-button-highlight-color, var(--rs-button-foreground-color)); + &.--highlighted { + z-index: var(--rs-z-index-relative); - /* Avoid edge cases when hovering causes icons to jump in Safari */ - transform: translateZ(0); + /* If there is a highlight element, it's used for the background color instead */ + &:not(:has(.highlight)), + & .highlight { + background-color: var(--rs-button-highlight-color); + } } @media (hover: hover) and (pointer: fine) { - &:hover:not(.--loading, .--highlighted, .--disabled)::before { - opacity: var(--rs-button-highlight-opacity); + &:hover:not(:has(.highlight), .--loading), + &:hover:not(.--loading) .highlight { + background-color: var(--rs-button-highlight-color); } } - - &.--highlighted::before, - &:active:not(.--loading, .--disabled)::before { - opacity: calc( - var(--rs-button-highlight-opacity) + max(0.04, var(--rs-button-highlight-opacity) / 2) - ); - } } .text { display: flex; align-items: center; gap: var(--rs-button-gap); + + & > :is(:first-child):not(.label, :only-child) { + margin-inline-start: calc(var(--rs-button-icon-align) * -1); + } + + &:not(:only-child) > :is(:last-child):not(.label, :only-child) { + margin-inline-end: calc(var(--rs-button-icon-align) * -1); + } } .icon { /* Starts with 1x and then grows based on the padding size */ margin-inline-end: var(--rs-button-gap); - - /* Icon only */ - &:last-child { - margin-inline-end: 0; - } + margin-inline-start: calc(var(--rs-button-icon-align) * -1); &.--icon-position-end { - margin-inline-end: 0; + margin-inline-end: calc(var(--rs-button-icon-align) * -1); margin-inline-start: var(--rs-button-gap); } } +.--icon-only .icon { + margin: 0; +} + .text, .icon { position: relative; - z-index: 5; + z-index: var(--rs-z-index-relative); } .loader { @@ -102,6 +100,19 @@ display: none; align-items: center; justify-content: center; + z-index: var(--rs-z-index-relative); +} + +.highlight { + position: absolute; + inset: 0; + pointer-events: none; + border-radius: inherit; + background-color: transparent; + transition: var(--rs-duration-fast) var(--rs-easing-standard); + + /* Avoid edge cases when hovering causes icons to jump in Safari */ + transform: translateZ(0); } .root.--icon-only { @@ -133,10 +144,11 @@ @value small { --rs-button-p-v: var(--rs-unit-x1); --rs-button-p-h: var(--rs-unit-x2); - --rs-button-gap: var(--rs-unit-x1); - --rs-button-line-height: var(--rs-line-height-body-3); - --rs-button-font-size: var(--rs-font-size-body-3); - --rs-button-letter-spacing: var(--rs-letter-spacing-body-3); + --rs-button-gap: var(--rs-unit-x1-5); + --rs-button-icon-align: var(--rs-unit-x0-5); + --rs-button-line-height: var(--rs-line-height-body-2); + --rs-button-font-size: var(--rs-font-size-body-2); + --rs-button-letter-spacing: var(--rs-letter-spacing-body-2); --rs-button-radius: var(--rs-radius-small); } @@ -144,19 +156,21 @@ --rs-button-p-v: var(--rs-unit-x2); --rs-button-p-h: var(--rs-unit-x3); --rs-button-gap: var(--rs-unit-x2); - --rs-button-line-height: var(--rs-line-height-body-3); - --rs-button-font-size: var(--rs-font-size-body-3); - --rs-button-letter-spacing: var(--rs-letter-spacing-body-3); - --rs-button-radius: var(--rs-radius-small); + --rs-button-icon-align: var(--rs-unit-x0-5); + --rs-button-line-height: var(--rs-line-height-body-2); + --rs-button-font-size: var(--rs-font-size-body-2); + --rs-button-letter-spacing: var(--rs-letter-spacing-body-2); + --rs-button-radius: var(--rs-radius-medium); } @value large { --rs-button-p-v: var(--rs-unit-x3); --rs-button-p-h: var(--rs-unit-x4); --rs-button-gap: var(--rs-unit-x2); - --rs-button-line-height: var(--rs-line-height-body-2); - --rs-button-font-size: var(--rs-font-size-body-2); - --rs-button-letter-spacing: var(--rs-letter-spacing-body-2); + --rs-button-icon-align: var(--rs-unit-x1); + --rs-button-line-height: var(--rs-line-height-body-1); + --rs-button-font-size: var(--rs-font-size-body-1); + --rs-button-letter-spacing: var(--rs-letter-spacing-body-1); --rs-button-radius: var(--rs-radius-medium); } @@ -164,9 +178,10 @@ --rs-button-p-v: var(--rs-unit-x4); --rs-button-p-h: var(--rs-unit-x5); --rs-button-gap: var(--rs-unit-x3); - --rs-button-line-height: var(--rs-line-height-body-2); - --rs-button-font-size: var(--rs-font-size-body-2); - --rs-button-letter-spacing: var(--rs-letter-spacing-body-2); + --rs-button-icon-align: var(--rs-unit-x1); + --rs-button-line-height: var(--rs-line-height-title-6); + --rs-button-font-size: var(--rs-font-size-title-6); + --rs-button-letter-spacing: var(--rs-letter-spacing-body-1); --rs-button-radius: var(--rs-radius-medium); } } @@ -184,268 +199,255 @@ } .root.--variant-solid { + --rs-button-border-width: 0px; + &.--color-neutral { - --rs-button-highlight-opacity: 0.04; + --rs-button-group-separator-color: var(--rs-color-border-neutral-faded); --rs-button-background-color: var(--rs-color-background-neutral); - --rs-button-foreground-color: var(--rs-color-on-background-neutral); + --rs-button-highlight-color: var(--rs-color-background-neutral-highlighted); + --rs-button-color: var(--rs-color-on-background-neutral); } @each $color in primary, critical, positive { &.--color-$(color) { - --rs-button-highlight-opacity: 0.08; + --rs-button-group-separator-color: var(--rs-color-border-$(color)); --rs-button-background-color: var(--rs-color-background-$(color)); - --rs-button-foreground-color: var(--rs-color-on-background-$(color)); + --rs-button-highlight-color: var(--rs-color-background-$(color)-highlighted); + --rs-button-color: var(--rs-color-on-background-$(color)); } } &.--color-media { - --rs-button-highlight-opacity: 0.06; - --rs-button-background-color: var(--rs-color-white); - --rs-button-foreground-color: var(--rs-color-black); + --rs-button-group-separator-color: rgb(from var(--rs-color-white) r g b / 12%); + --rs-button-color: var(--rs-color-white); + --rs-button-background-color: rgb(from var(--rs-color-black) r g b / 28%); + --rs-button-highlight-color: rgb(from var(--rs-color-black) r g b / 36%); } } -.root.--variant-faded { - @each $color in neutral, primary, critical, positive { - &.--color-$(color) { - --rs-button-highlight-opacity: 0.04; - --rs-button-background-color: var(--rs-color-background-$(color)-faded); - --rs-button-foreground-color: var(--rs-color-foreground-$(color)); - } - } +.root.--variant-outline { + --rs-button-border-width: 1px; - &.--color-media { - --rs-button-highlight-opacity-base: 0.24; - --rs-button-highlight-opacity: 0.32; - --rs-button-foreground-color: var(--rs-color-white); - --rs-button-highlight-color: var(--rs-color-black); + &::before { + content: ""; + position: absolute; + pointer-events: none; + border-radius: inherit; + z-index: calc(var(--rs-z-index-relative) - 1); + inset: 0; + border: var(--rs-button-border-width) solid var(--rs-color-border-neutral); } - &.--color-inherit { - --rs-button-highlight-opacity-base: 0.12; - --rs-button-highlight-opacity: 0.16; - --rs-button-foreground-color: inherit; - --rs-button-highlight-color: currentcolor; + &::after { + box-shadow: var(--rs-shadow-outline); } -} - -.root.--variant-outline { - --rs-button-border-width: 1px; - --rs-button-highlight-opacity: 0.06; - @each $color in primary, critical, positive { + @each $color in neutral, primary, critical, positive { &.--color-$(color) { - --rs-button-foreground-color: var(--rs-color-foreground-$(color)); - --rs-button-border-color: var(--rs-color-border-$(color)-faded); - --rs-button-highlight-color: var(--rs-color-background-$(color)); + --rs-button-group-separator-color: var(--rs-color-border-neutral); + --rs-button-color: var(--rs-color-foreground-$(color)); + --rs-button-background-color: var(--rs-color-background-elevation-base); + --rs-button-highlight-color: var(--rs-color-background-neutral-highlighted-faded); } } - &.--color-neutral { - --rs-button-highlight-opacity: 0.24; - --rs-button-foreground-color: var(--rs-color-foreground-neutral); - --rs-button-border-color: var(--rs-color-border-neutral); - --rs-button-highlight-color: var(--rs-color-background-neutral); - } - &.--color-inherit { - --rs-button-border-width: 0px; - --rs-button-foreground-color: inherit; - --rs-button-highlight-color: currentcolor; - - &::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - border: 1px solid currentcolor; - border-radius: var(--rs-button-radius); - opacity: 0.28; - } + --rs-button-group-separator-color: var(--rs-color-border-neutral); + --rs-button-color: currentcolor; + --rs-button-background-color: var(--rs-color-background-elevation-base); + --rs-button-highlight-color: var(--rs-color-background-neutral-highlighted-faded); } } .root.--variant-ghost { - --rs-button-highlight-opacity: 0.12; + --rs-button-border-width: 0px; - &.--color-neutral { - --rs-button-highlight-opacity: 0.32; - } + transition-duration: var(--rs-duration-rapid); @each $color in neutral, primary, critical, positive { &.--color-$(color) { - --rs-button-foreground-color: var(--rs-color-foreground-$(color)); - --rs-button-highlight-color: var(--rs-color-background-$(color)); + --rs-button-group-separator-color: var(--rs-color-border-neutral-faded); + --rs-button-color: var(--rs-color-foreground-$(color)); + --rs-button-background-color: transparent; + --rs-button-highlight-color: var(--rs-color-background-$(color)-highlighted-faded); } } &.--color-inherit { - --rs-button-foreground-color: inherit; + --rs-button-group-separator-color: var(--rs-color-border-neutral-faded); + --rs-button-color: inherit; --rs-button-highlight-color: currentcolor; + + & .highlight { + opacity: 0.08; + } } } -.root.--elevated { - [data-rs-keyboard] &:not(:focus), - &:not(:focus) { - box-shadow: var(--rs-shadow-raised); +.root.--variant-outline, +.root.--raised { + &::after { + content: ""; + position: absolute; + inset: var(--rs-button-border-width); + pointer-events: none; + border-radius: inherit; + z-index: var(--rs-z-index-relative); } +} +.root.--raised { + &::after { + box-shadow: var(--rs-shadow-raised-intense); + } + + &.--variant-solid.--color-neutral, &.--variant-outline { - &.--color-neutral, - &.--color-primary, - &.--color-critical, - &.--color-positive { - background: var(--rs-color-background-elevation-raised); + &::after { + box-shadow: var(--rs-shadow-raised); } } -} -.root.--disabled { - &.--color-neutral, - &.--color-primary, - &.--color-positive, - &.--color-critical, - &.--color-inherit { - &, - &:hover, - &:active, - &.--highlighted { - color: var(--rs-color-foreground-disabled) !important; - border-color: var(--rs-color-border-disabled) !important; - background-color: var(--rs-color-background-disabled) !important; + &.--variant-outline { + background-color: var(--rs-color-background-elevation-raised); + } +} - &::before { - opacity: 0 !important; - } +.root.--disabled:not(.--color-media) { + &, + &:hover:not(.--loading), + &:active:not(.--loading), + &.--highlighted { + color: var(--rs-color-foreground-disabled) !important; + background-color: var(--rs-color-background-disabled) !important; + + & .highlight { + opacity: 0 !important; + } - &.--variant-faded { - background-color: var(--rs-color-background-disabled-faded) !important; - } + &.--variant-outline { + background-color: transparent !important; - &.--variant-outline { - background-color: transparent !important; + &::before { + border-color: var(--rs-color-border-disabled) !important; } - &.--variant-ghost { - background-color: transparent !important; - border-color: transparent !important; + &::after { + content: none !important; } } - } - &.--color-media { - &, - &:hover, - &:active, - &.--highlighted { - opacity: 0.4 !important; + &.--variant-ghost { + background-color: transparent !important; } } } -.root.--highlighted { - z-index: var(--rs-z-index-relative); +.root.--color-media.--disabled { + &, + &:hover, + &:active, + &.--highlighted { + opacity: 0.6 !important; + background-color: var(--rs-button-background-color) !important; + } } /* Button group */ .group { - display: flex; -} - -.group .root { - flex-shrink: 0; - width: auto; + display: inline-flex; + position: relative; - &, - &::before { - border-radius: 0; + &:has(.--full-width) { + display: flex; } - &:first-child { - &, - &::before { - border-start-start-radius: var(--rs-button-radius); - border-end-start-radius: var(--rs-button-radius); + &:has(.--variant-outline) { + &::before, + &::after { + border-radius: var(--rs-radius-medium); } - } - &:last-child { - &, - &::before { - border-start-end-radius: var(--rs-button-radius); - border-end-end-radius: var(--rs-button-radius); + &:has(.--size-small) { + border-radius: var(--rs-radius-small); } - } - - &:not(:first-child) { - border-inline-start: 1px solid var(--rs-button-group-separator-color); - /* Compensate left separator width so label alignment doesn't shift */ - padding-inline-start: calc(var(--rs-p-h) - 1px); - } - - &:not(:last-child) { - border-inline-end: 0; - border-inline-end: 0 solid var(--rs-button-group-separator-color); - - /* Keep internal horizontal space equal across grouped variants */ - padding-inline-end: var(--rs-p-h); - } + &::before { + content: ""; + position: absolute; + pointer-events: none; + z-index: var(--rs-z-index-relative); + inset: 0; + border: 1px solid var(--rs-color-border-neutral); + border-radius: 6px; + } - &.--highlighted:not(:last-child) { &::after { content: ""; position: absolute; - inset-block: calc(var(--rs-button-border-width) * -1); - inset-inline-end: -1px; + inset: 1px; pointer-events: none; - border-inline-end: 1px solid var(--rs-button-group-separator-color); + border-radius: inherit; + z-index: var(--rs-z-index-relative); } + } +} - & + .root { - border-inline-start-width: 0; +.group .root { + flex-shrink: 0; + width: auto; - /* Keep text position stable after removing the 1px group separator */ - padding-inline-start: var(--rs-p-h); - } + &:active { + transform: scale(1) translateZ(0); } - @each $color in neutral, positive, critical, primary { - &.--variant-solid.--color-$(color) { - --rs-button-group-separator-color: transparent; - } + &, + & .highlight { + border-radius: 0; } - &.--variant-faded.--color-neutral, - &.--variant-outline.--color-neutral { - --rs-button-group-separator-color: var(--rs-color-border-neutral); + &:first-child { + &, + & .highlight { + border-start-start-radius: var(--rs-button-radius); + border-end-start-radius: var(--rs-button-radius); + } } - @each $color in positive, critical, primary { - &.--variant-outline.--color-$(color), - &.--variant-faded.--color-$(color) { - --rs-button-group-separator-color: var(--rs-color-border-$(color)-faded); + &:last-child { + &, + & .highlight { + border-start-end-radius: var(--rs-button-radius); + border-end-end-radius: var(--rs-button-radius); } } - @each $color in neutral, positive, critical, primary { - &.--variant-ghost.--color-$(color) { - --rs-button-group-separator-color: var(--rs-color-border-$(color)-faded); - } + /* Reset outline border */ + &::before { + border: none; + inset-inline-end: auto; + border-radius: 0; + transition: background-color var(--rs-duration-fast) var(--rs-easing-standard); } - &.--variant-solid.--color-media { - --rs-button-group-separator-color: rgba(var(--rs-color-rgb-black), 0.12); + /* Reset outline shadow */ + &::after { + content: none; } - &.--variant-faded.--color-media { - --rs-button-group-separator-color: rgba(var(--rs-color-rgb-white), 0.12); + &:not(:first-child)::before { + content: ""; + position: absolute; + width: 1px; + inset-block: var(--rs-unit-x1); + inset-inline-start: -0.5px; + border-radius: var(--rs-radius-circular); + background-color: var(--rs-button-group-separator-color); } - &.--disabled { - --rs-button-group-separator-color: var(--rs-color-border-disabled) !important; + &.--variant-outline { + background: transparent; } &.--full-width { diff --git a/packages/reshaped/src/components/Button/Button.tsx b/packages/reshaped/src/components/Button/Button.tsx index 917c8d78..0f23e9c3 100644 --- a/packages/reshaped/src/components/Button/Button.tsx +++ b/packages/reshaped/src/components/Button/Button.tsx @@ -1,20 +1,19 @@ -import { classNames } from "@reshaped/headless"; import { forwardRef } from "react"; +import React from "react"; +import { classNames } from "@reshaped/utilities"; import Actionable, { type ActionableRef } from "@/components/Actionable"; import Icon from "@/components/Icon"; import Loader from "@/components/Loader"; import { responsiveClassNames, responsivePropDependency } from "@/utilities/props"; - -import s from "./Button.module.css"; - import type * as T from "./Button.types"; +import s from "./Button.module.css"; const Button = forwardRef((props, ref) => { const { variant = "solid", color = "neutral", - elevated, + raised, highlighted, fullWidth, loading, @@ -42,7 +41,7 @@ const Button = forwardRef((props, ref) => { variant && s[`--variant-${variant}`], responsiveClassNames(s, "--size", size), responsiveClassNames(s, "--full-width", fullWidth), - elevated && variant !== "ghost" && s["--elevated"], + raised && variant !== "ghost" && color !== "media" && s["--raised"], rounded && s["--rounded"], disabled && s["--disabled"], loading && s["--loading"], @@ -96,8 +95,19 @@ const Button = forwardRef((props, ref) => {
)} {renderIcon("start")} - {children && {children}} + {children && ( + + {React.Children.map(children, (child) => { + if (typeof child === "string") { + return {child}; + } + + return child; + })} + + )} {renderIcon("end")} + {["outline", "ghost"].includes(variant) && } ); }); diff --git a/packages/reshaped/src/components/Button/Button.types.ts b/packages/reshaped/src/components/Button/Button.types.ts index d2b82f82..bfc153bd 100644 --- a/packages/reshaped/src/components/Button/Button.types.ts +++ b/packages/reshaped/src/components/Button/Button.types.ts @@ -1,9 +1,10 @@ -import type { AlignerProps as BaseAlignerProps } from "@/components/_private/Aligner"; +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type { ActionableProps } from "@/components/Actionable"; import type { IconProps } from "@/components/Icon"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; export type Size = "xlarge" | "large" | "medium" | "small"; @@ -23,11 +24,11 @@ export type Props = Pick< /** Component color scheme * @default "neutral" */ - color?: "primary" | "critical" | "positive" | "neutral" | "media" | "inherit"; + color?: "primary" | "critical" | "positive" | "neutral" | "warning" | "media" | "inherit"; /** Component render variant * @default "solid" */ - variant?: "solid" | "outline" | "ghost" | "faded"; + variant?: "solid" | "outline" | "ghost"; /** SVG component for the icon */ icon?: IconProps["svg"]; /** SVG component for the end position icon */ @@ -42,8 +43,8 @@ export type Props = Pick< loading?: boolean; /** aria-label attribute for the loading indicator */ loadingAriaLabel?: string; - /** Apply elevated styles to the component */ - elevated?: boolean; + /** Apply raised styles to the component */ + raised?: boolean; /** Make the component take the full width of the parent element */ fullWidth?: G.Responsive; /** Highlight the component when component is used for an active state */ @@ -59,9 +60,4 @@ export type GroupProps = { attributes?: Attributes<"div">; }; -export type AlignerProps = BaseAlignerProps & { - /** - * @deprecated use `side` instead, will be remove in v4 - */ - position?: BaseAlignerProps["side"]; -}; +export type { AlignerProps } from "@/components/_private/Aligner"; diff --git a/packages/reshaped/src/components/Button/ButtonAligner.tsx b/packages/reshaped/src/components/Button/ButtonAligner.tsx index b1c89768..824391f1 100644 --- a/packages/reshaped/src/components/Button/ButtonAligner.tsx +++ b/packages/reshaped/src/components/Button/ButtonAligner.tsx @@ -1,17 +1,9 @@ import Aligner from "@/components/_private/Aligner"; - -import s from "./Button.module.css"; - import type * as T from "./Button.types"; +import s from "./Button.module.css"; const ButtonAligner: React.FC = (props) => { - return ( - - ); + return ; }; ButtonAligner.displayName = "Button.Aligner"; diff --git a/packages/reshaped/src/components/Button/ButtonGroup.tsx b/packages/reshaped/src/components/Button/ButtonGroup.tsx index c3ec0a41..517615ce 100644 --- a/packages/reshaped/src/components/Button/ButtonGroup.tsx +++ b/packages/reshaped/src/components/Button/ButtonGroup.tsx @@ -1,8 +1,7 @@ -import { classNames } from "@reshaped/headless"; - -import s from "./Button.module.css"; +import { classNames } from "@reshaped/utilities"; import type * as T from "./Button.types"; +import s from "./Button.module.css"; const ButtonGroup: React.FC = (props) => { const { children, className, attributes } = props; diff --git a/packages/reshaped/src/components/Button/index.ts b/packages/reshaped/src/components/Button/index.ts index 75e8defc..00d9a20d 100644 --- a/packages/reshaped/src/components/Button/index.ts +++ b/packages/reshaped/src/components/Button/index.ts @@ -12,7 +12,7 @@ ButtonRoot.Group = ButtonGroup; export default ButtonRoot; export type { - Props as ButtonProps, AlignerProps as ButtonAlignerProps, GroupProps as ButtonGroupProps, + Props as ButtonProps, } from "./Button.types"; diff --git a/packages/reshaped/src/components/Button/tests/Button.stories.tsx b/packages/reshaped/src/components/Button/tests/Button.stories.tsx index 5f1f0efe..57115594 100644 --- a/packages/reshaped/src/components/Button/tests/Button.stories.tsx +++ b/packages/reshaped/src/components/Button/tests/Button.stories.tsx @@ -2,12 +2,14 @@ import { StoryObj } from "@storybook/react-vite"; import { expect, fn, userEvent } from "storybook/test"; import Avatar from "@/components/Avatar"; +import Badge from "@/components/Badge"; import Button from "@/components/Button"; -import Hotkey from "@/components/Hotkey"; import Image from "@/components/Image"; +import Text from "@/components/Text"; import View from "@/components/View"; -import IconZap from "@/icons/Zap"; import { Example, Placeholder } from "@/utilities/storybook"; +import IconPlus from "@/icons/Plus"; +import IconZap from "@/icons/Zap"; export default { title: "Components/Button", @@ -27,78 +29,41 @@ export const variantAndColor = { render: () => ( - - + + - - - - - - - - - - - - - - - - - + - - - - - - - - + @@ -106,48 +71,38 @@ export const variantAndColor = { - + - + - + - + + - - - - - - - ), }; @@ -157,7 +112,7 @@ export const icon = { render: () => ( - @@ -248,16 +203,16 @@ export const size = { ), }; -export const elevated = { - name: "elevated", +export const raised = { + name: "raised", render: () => ( - - @@ -265,17 +220,20 @@ export const elevated = { - - + + + + + + - - - @@ -343,9 +301,6 @@ export const fullWidth = { - @@ -371,9 +326,6 @@ export const loading = { - @@ -387,9 +339,6 @@ export const loading = { - @@ -403,9 +352,6 @@ export const loading = { - @@ -424,9 +370,6 @@ export const loading = { -
@@ -454,9 +397,6 @@ export const highlighted = { - @@ -470,9 +410,6 @@ export const highlighted = { - @@ -486,9 +423,6 @@ export const highlighted = { - @@ -510,9 +444,6 @@ export const disabled: StoryObj = { - @@ -526,9 +457,6 @@ export const disabled: StoryObj = { - @@ -542,9 +470,6 @@ export const disabled: StoryObj = { - @@ -563,9 +488,6 @@ export const disabled: StoryObj = { - @@ -654,11 +576,22 @@ export const composition = { name: "test: composition", render: () => ( - + + + + + + + + @@ -781,18 +714,25 @@ export const group: StoryObj = { - - {(["neutral", "primary", "critical", "positive", "media"] as const).map((color) => ( + + {(["neutral", "primary", "critical", "positive"] as const).map((color) => ( ))} + + + + + + + - + {(["neutral", "primary", "critical", "positive"] as const).map((color) => ( - - - - ))} - - - + {(["neutral", "primary", "critical", "positive"] as const).map((color) => ( }> + + + + + + + ); + }, +}; + +export const raised = { + name: "raised", render: () => ( - - + + @@ -90,13 +123,20 @@ export const bleed = { ), }; -export const height = { - name: "height", +export const layout = { + name: "height, direction, gap, align, justify", render: () => ( + + + + + + + ), }; diff --git a/packages/reshaped/src/components/Carousel/Carousel.tsx b/packages/reshaped/src/components/Carousel/Carousel.tsx index 209151b7..c126567a 100644 --- a/packages/reshaped/src/components/Carousel/Carousel.tsx +++ b/packages/reshaped/src/components/Carousel/Carousel.tsx @@ -1,17 +1,17 @@ "use client"; -import { classNames, useIsomorphicLayoutEffect, useRTL } from "@reshaped/headless"; -import { rafThrottle } from "@reshaped/headless/internal"; import React from "react"; +import { classNames } from "@reshaped/utilities"; +import { rafThrottle } from "@reshaped/utilities/internal"; +import type { ActionableRef } from "@/components/Actionable"; import View from "@/components/View"; -import { responsiveVariables, responsiveClassNames } from "@/utilities/props"; - -import s from "./Carousel.module.css"; +import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect"; +import useRTL from "@/hooks/useRTL"; +import { responsiveClassNames, responsiveVariables } from "@/utilities/props"; import * as T from "./Carousel.types"; import CarouselControl from "./CarouselControl"; - -import type { ActionableRef } from "@/components/Actionable"; +import s from "./Carousel.module.css"; const Carousel: React.FC = (props) => { const { diff --git a/packages/reshaped/src/components/Carousel/Carousel.types.ts b/packages/reshaped/src/components/Carousel/Carousel.types.ts index ce61a503..3d3ec5a7 100644 --- a/packages/reshaped/src/components/Carousel/Carousel.types.ts +++ b/packages/reshaped/src/components/Carousel/Carousel.types.ts @@ -1,7 +1,9 @@ +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type { ActionableRef } from "@/components/Actionable"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; export type Instance = | { diff --git a/packages/reshaped/src/components/Carousel/CarouselControl.tsx b/packages/reshaped/src/components/Carousel/CarouselControl.tsx index 59495cbe..5a59839e 100644 --- a/packages/reshaped/src/components/Carousel/CarouselControl.tsx +++ b/packages/reshaped/src/components/Carousel/CarouselControl.tsx @@ -1,16 +1,15 @@ "use client"; -import { classNames, useIsomorphicLayoutEffect } from "@reshaped/headless"; import { forwardRef, useState } from "react"; +import { classNames } from "@reshaped/utilities"; +import type { ActionableRef } from "@/components/Actionable"; import Button from "@/components/Button"; +import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect"; import IconChevronLeft from "@/icons/ChevronLeft"; import IconChevronRight from "@/icons/ChevronRight"; - -import s from "./Carousel.module.css"; import * as T from "./Carousel.types"; - -import type { ActionableRef } from "@/components/Actionable"; +import s from "./Carousel.module.css"; const CarouselControl = forwardRef((props, ref) => { const { type, scrollElRef, oppositeControlElRef, scrollPosition, onClick, isRTL, mounted } = @@ -60,7 +59,7 @@ const CarouselControl = forwardRef((props, ref) = icon={isDisplayedAsNext ? IconChevronRight : IconChevronLeft} rounded variant="outline" - elevated + raised attributes={{ "aria-disabled": !visible, "aria-hidden": true }} ref={ref} /> diff --git a/packages/reshaped/src/components/Carousel/index.ts b/packages/reshaped/src/components/Carousel/index.ts index 86dee091..c6fb3fe3 100644 --- a/packages/reshaped/src/components/Carousel/index.ts +++ b/packages/reshaped/src/components/Carousel/index.ts @@ -1,6 +1,2 @@ export { default } from "./Carousel"; -export type { - Props as CarouselProps, - Instance as CarouselInstanceRef, - Instance as CarouselInstance, -} from "./Carousel.types"; +export type { Instance as CarouselInstance, Props as CarouselProps } from "./Carousel.types"; diff --git a/packages/reshaped/src/components/Carousel/tests/Carousel.stories.tsx b/packages/reshaped/src/components/Carousel/tests/Carousel.stories.tsx index 5b2d2bb2..571003f7 100644 --- a/packages/reshaped/src/components/Carousel/tests/Carousel.stories.tsx +++ b/packages/reshaped/src/components/Carousel/tests/Carousel.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import { expect, fn, userEvent, waitFor } from "storybook/test"; import Button from "@/components/Button"; -import Carousel, { type CarouselInstanceRef } from "@/components/Carousel"; +import Carousel, { type CarouselInstance } from "@/components/Carousel"; import View from "@/components/View"; import { Example, Placeholder } from "@/utilities/storybook"; @@ -177,7 +177,7 @@ export const instanceRef: StoryObj<{ handleChange: ReturnType }> = { handleChange: fn(), }, render: (args) => { - const carouselRef = React.useRef(null); + const carouselRef = React.useRef(null); const [index, setIndex] = React.useState(0); return ( diff --git a/packages/reshaped/src/components/Checkbox/Checkbox.module.css b/packages/reshaped/src/components/Checkbox/Checkbox.module.css index 0fbdb63f..5ef6dc71 100644 --- a/packages/reshaped/src/components/Checkbox/Checkbox.module.css +++ b/packages/reshaped/src/components/Checkbox/Checkbox.module.css @@ -58,16 +58,16 @@ since it reset the border width/color without using @layer @responsive .--size { @value small { --rs-checkbox-line-height: var(--rs-line-height-caption-1); - --rs-checkbox-gap: var(--rs-unit-x1); + --rs-checkbox-gap: var(--rs-unit-x1-5); } @value medium { - --rs-checkbox-line-height: var(--rs-line-height-body-3); + --rs-checkbox-line-height: var(--rs-line-height-body-2); --rs-checkbox-gap: var(--rs-unit-x2); } @value large { - --rs-checkbox-line-height: var(--rs-line-height-body-2); + --rs-checkbox-line-height: var(--rs-line-height-body-1); --rs-checkbox-gap: var(--rs-unit-x2); } } @@ -82,8 +82,9 @@ since it reset the border width/color without using @layer transition-property: opacity, transform; } -[data-rs-keyboard] .input:focus-visible + .decorator { - box-shadow: var(--rs-shadow-focus); +[data-rs-keyboard] .input:focus + .decorator { + outline: var(--rs-outline); + outline-offset: var(--rs-outline-width); } /* } */ @@ -91,6 +92,7 @@ since it reset the border width/color without using @layer /* @layer rs.checkbox.error { */ .root.--error .decorator { border-color: var(--rs-color-border-critical); + border-width: var(--rs-outline-width); } /* } */ diff --git a/packages/reshaped/src/components/Checkbox/Checkbox.tsx b/packages/reshaped/src/components/Checkbox/Checkbox.tsx index f150b621..59468312 100644 --- a/packages/reshaped/src/components/Checkbox/Checkbox.tsx +++ b/packages/reshaped/src/components/Checkbox/Checkbox.tsx @@ -1,27 +1,24 @@ "use client"; -import { classNames, useIsomorphicLayoutEffect } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; import { useCheckboxGroup } from "@/components/CheckboxGroup"; import { useFormControl } from "@/components/FormControl"; import HiddenInput from "@/components/HiddenInput"; import Icon from "@/components/Icon"; import Text from "@/components/Text"; -import IconCheckmark from "@/icons/Checkmark"; +import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect"; import { responsiveClassNames, responsivePropDependency } from "@/utilities/props"; - -import s from "./Checkbox.module.css"; - +import IconCheckmark from "@/icons/Checkmark"; import type * as T from "./Checkbox.types"; +import s from "./Checkbox.module.css"; const Checkbox: React.FC = (props) => { const { children, value, onChange, - onFocus, - onBlur, indeterminate, size = "medium", className, @@ -61,8 +58,6 @@ const Checkbox: React.FC = (props) => { disabled={disabled} value={value} onChange={onChange} - onFocus={onFocus} - onBlur={onBlur} attributes={{ ...inputAttributes, ref: inputRef, @@ -85,9 +80,9 @@ const Checkbox: React.FC = (props) => { { - if (size === "large") return "body-2"; + if (size === "large") return "body-1"; if (size === "small") return "caption-1"; - return "body-3"; + return "body-2"; })} > {children} diff --git a/packages/reshaped/src/components/Checkbox/Checkbox.types.ts b/packages/reshaped/src/components/Checkbox/Checkbox.types.ts index 695f8c2e..78d31d9f 100644 --- a/packages/reshaped/src/components/Checkbox/Checkbox.types.ts +++ b/packages/reshaped/src/components/Checkbox/Checkbox.types.ts @@ -1,6 +1,8 @@ -import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + +import type * as G from "@/types/global"; +import type { Attributes } from "@/types/global"; type BaseProps = { /** Node for inserting the label */ @@ -21,10 +23,6 @@ type BaseProps = { size?: G.Responsive<"small" | "medium" | "large">; /** Callback when the component selection changes */ onChange?: G.ChangeHandler; - /** Callback when the component is focused */ - onFocus?: (e: React.FocusEvent) => void; - /** Callback when the component is blurred */ - onBlur?: (e: React.FocusEvent) => void; /** Additional classname for the root element */ className?: ClassName; /** Additional attributes for the root element */ diff --git a/packages/reshaped/src/components/Checkbox/tests/Checkbox.stories.tsx b/packages/reshaped/src/components/Checkbox/tests/Checkbox.stories.tsx index 4f369c63..0cfe3609 100644 --- a/packages/reshaped/src/components/Checkbox/tests/Checkbox.stories.tsx +++ b/packages/reshaped/src/components/Checkbox/tests/Checkbox.stories.tsx @@ -187,10 +187,6 @@ export const indeterminate: StoryObj = { const input = canvas.getByRole("checkbox") as HTMLInputElement; expect(input.indeterminate).toBeTruthy(); - - await userEvent.click(input); - - expect(input.indeterminate).toBeFalsy(); }, }; diff --git a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.tsx index 5c1a9bb8..201950c0 100644 --- a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -1,8 +1,7 @@ +import type * as T from "./CheckboxGroup.types"; import CheckboxGroupControlled from "./CheckboxGroupControlled"; import CheckboxGroupUncontrolled from "./CheckboxGroupUncontrolled"; -import type * as T from "./CheckboxGroup.types"; - const CheckboxGroup: React.FC = (props) => { const { value } = props; diff --git a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.types.ts b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.types.ts index d9262114..98c45c22 100644 --- a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.types.ts +++ b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroup.types.ts @@ -1,6 +1,7 @@ +import type React from "react"; + import type { CheckboxProps } from "@/components/Checkbox"; import type * as G from "@/types/global"; -import type React from "react"; type BaseProps = { /** Component id attribute */ diff --git a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupControlled.tsx b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupControlled.tsx index c22d74b8..0fdc7c68 100644 --- a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupControlled.tsx +++ b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupControlled.tsx @@ -1,7 +1,6 @@ "use client"; import Context from "./CheckboxGroup.context"; - import type * as T from "./CheckboxGroup.types"; const CheckboxGroupControlled: React.FC = (props) => { diff --git a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupUncontrolled.tsx b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupUncontrolled.tsx index 9ad1c72f..3858475b 100644 --- a/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupUncontrolled.tsx +++ b/packages/reshaped/src/components/CheckboxGroup/CheckboxGroupUncontrolled.tsx @@ -2,9 +2,8 @@ import React from "react"; -import CheckboxGroupControlled from "./CheckboxGroupControlled"; - import type * as T from "./CheckboxGroup.types"; +import CheckboxGroupControlled from "./CheckboxGroupControlled"; const CheckboxGroupUncontrolled: React.FC = (props) => { const { defaultValue, onChange } = props; diff --git a/packages/reshaped/src/components/CheckboxGroup/index.ts b/packages/reshaped/src/components/CheckboxGroup/index.ts index 64484ffa..076dae9c 100644 --- a/packages/reshaped/src/components/CheckboxGroup/index.ts +++ b/packages/reshaped/src/components/CheckboxGroup/index.ts @@ -1,3 +1,3 @@ export { default } from "./CheckboxGroup"; -export type { Props as CheckboxGroupProps } from "./CheckboxGroup.types"; export { useCheckboxGroup } from "./CheckboxGroup.context"; +export type { Props as CheckboxGroupProps } from "./CheckboxGroup.types"; diff --git a/packages/reshaped/src/components/Container/Container.tsx b/packages/reshaped/src/components/Container/Container.tsx index 80494356..04ffff8e 100644 --- a/packages/reshaped/src/components/Container/Container.tsx +++ b/packages/reshaped/src/components/Container/Container.tsx @@ -1,10 +1,8 @@ -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; import View from "@/components/View"; - -import s from "./Container.module.css"; - import type * as T from "./Container.types"; +import s from "./Container.module.css"; const Container: React.FC = (props) => { const { diff --git a/packages/reshaped/src/components/Container/Container.types.ts b/packages/reshaped/src/components/Container/Container.types.ts index 094bc2c3..82e37943 100644 --- a/packages/reshaped/src/components/Container/Container.types.ts +++ b/packages/reshaped/src/components/Container/Container.types.ts @@ -1,17 +1,15 @@ +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type { ViewProps } from "@/components/View"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; export type Props = Pick & { /** Component inline padding */ padding?: ViewProps["padding"]; /** Component width, literal css value or unit token multiplier */ width?: G.Responsive; - /** Component height, literal css value or unit token multiplier */ - height?: G.Responsive; - /** Component max height, literal css value or unit token multiplier */ - maxHeight?: G.Responsive; /** Node for inserting children */ children?: React.ReactNode; /** Additional classname for the root element */ diff --git a/packages/reshaped/src/components/ContextMenu/ContextMenu.tsx b/packages/reshaped/src/components/ContextMenu/ContextMenu.tsx index 9053dca8..b5fe8d62 100644 --- a/packages/reshaped/src/components/ContextMenu/ContextMenu.tsx +++ b/packages/reshaped/src/components/ContextMenu/ContextMenu.tsx @@ -1,14 +1,13 @@ "use client"; -import { useHandlerRef, useScrollLock } from "@reshaped/headless"; import React from "react"; +import type { Coordinates } from "@reshaped/utilities/internal"; import DropdownMenu from "@/components/DropdownMenu"; - -import s from "./ContextMenu.module.css"; - +import useHandlerRef from "@/hooks/useHandlerRef"; +import useScrollLock from "@/hooks/useScrollLock"; import type * as T from "./ContextMenu.types"; -import type { Coordinates } from "@reshaped/headless/internal"; +import s from "./ContextMenu.module.css"; const ContextMenu: React.FC = (props) => { const { position = "end-top", onOpen, onClose, ...dropdownMenuProps } = props; diff --git a/packages/reshaped/src/components/ContextMenu/index.ts b/packages/reshaped/src/components/ContextMenu/index.ts index 9bd72a0a..b5a7c62a 100644 --- a/packages/reshaped/src/components/ContextMenu/index.ts +++ b/packages/reshaped/src/components/ContextMenu/index.ts @@ -1,5 +1,4 @@ import DropdownMenu from "@/components/DropdownMenu"; - import ContextMenu from "./ContextMenu"; const ContextMenuRoot = ContextMenu as typeof ContextMenu & { diff --git a/packages/reshaped/src/components/Dismissible/Dismissible.tsx b/packages/reshaped/src/components/Dismissible/Dismissible.tsx index df8d3e17..5e2327d5 100644 --- a/packages/reshaped/src/components/Dismissible/Dismissible.tsx +++ b/packages/reshaped/src/components/Dismissible/Dismissible.tsx @@ -1,13 +1,11 @@ "use client"; -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; import Button from "@/components/Button"; import IconClose from "@/icons/Close"; - -import s from "./Dismissible.module.css"; - import type * as T from "./Dismissible.types"; +import s from "./Dismissible.module.css"; const Dismissible: React.FC = (props) => { const { @@ -36,7 +34,7 @@ const Dismissible: React.FC = (props) => { } - - {children || "Content"} - + {children || "Content"} ); }; @@ -331,16 +335,18 @@ const FallbackAdjustLayoutControls = ({ fallbackAdjustLayout fallbackPositions={false} containerRef={containerRef} - children={content} - /> + > + {content} + + > + {content} + @@ -350,32 +356,36 @@ const FallbackAdjustLayoutControls = ({ fallbackAdjustLayout contentWidth={contentWidth} containerRef={containerRef} - children={content} - /> + > + {content} + + > + {content} + + > + {content} + + > + {content} + @@ -385,16 +395,18 @@ const FallbackAdjustLayoutControls = ({ fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef} - children={content} - /> + > + {content} + + > + {content} + {/* Right side */} @@ -406,16 +418,18 @@ const FallbackAdjustLayoutControls = ({ fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef} - children={content} - /> + > + {content} + + > + {content} + @@ -425,32 +439,36 @@ const FallbackAdjustLayoutControls = ({ fallbackAdjustLayout contentWidth={contentWidth} containerRef={containerRef} - children={content} - /> + > + {content} + + > + {content} + + > + {content} + + > + {content} + @@ -460,16 +478,18 @@ const FallbackAdjustLayoutControls = ({ fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef} - children={content} - /> + > + {content} + + > + {content} + ); @@ -1082,12 +1102,12 @@ export const testScopedTheming = { render: () => ( - + {(attributes) => ( )} @@ -1095,7 +1115,7 @@ export const testScopedTheming = { Portal content, rendered in body - + @@ -1126,31 +1146,38 @@ export const testWithoutFocusable: StoryObj = { export const testChangeSize = { name: "test: size updates", render: () => { - const [position, setPosition] = React.useState("bottom-start"); + const [position, setPosition] = + React.useState>("bottom-start"); const [updatedHeight, setUpdatedHeight] = React.useState(false); + const positions = [ + "bottom-start", + "bottom", + "bottom-end", + "top-start", + "top", + "top-end", + "start-top", + "start", + "start-bottom", + "end-top", + "end", + "end-bottom", + ] as const; return ( <> setUpdatedHeight(args.checked)}> Change height diff --git a/packages/reshaped/src/components/Flyout/useFlyout.ts b/packages/reshaped/src/components/Flyout/useFlyout.ts index 083e7fea..121f1044 100644 --- a/packages/reshaped/src/components/Flyout/useFlyout.ts +++ b/packages/reshaped/src/components/Flyout/useFlyout.ts @@ -1,7 +1,8 @@ -import { useIsomorphicLayoutEffect } from "@reshaped/headless"; -import { Flyout, type Coordinates } from "@reshaped/headless/internal"; import { useCallback, useMemo, useRef, useState } from "react"; +import { Flyout } from "@reshaped/utilities"; +import { type Coordinates } from "@reshaped/utilities/internal"; +import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect"; import type * as T from "./Flyout.types"; type UseFlyout = ( @@ -54,7 +55,7 @@ const useFlyout: UseFlyout = (args) => { // Memo the array internally to avoid new arrays triggering useCallback const cachedFallbackPositions = useMemo( () => fallbackPositions, - // eslint-disable-next-line react-hooks/exhaustive-deps + // oxlint-disable-next-line react-hooks/exhaustive-deps [fallbackPositions?.join(" ")] ); diff --git a/packages/reshaped/src/components/Flyout/utilities/safeArea.ts b/packages/reshaped/src/components/Flyout/utilities/safeArea.ts index fe6c3e60..bcd2629c 100644 --- a/packages/reshaped/src/components/Flyout/utilities/safeArea.ts +++ b/packages/reshaped/src/components/Flyout/utilities/safeArea.ts @@ -1,4 +1,4 @@ -import type { Coordinates } from "@reshaped/headless/internal"; +import type { Coordinates } from "@reshaped/utilities/internal"; type SafePolygonOptions = { contentRef: React.RefObject; diff --git a/packages/reshaped/src/components/FormControl/FormControl.module.css b/packages/reshaped/src/components/FormControl/FormControl.module.css index d4c7668a..97dc7b75 100644 --- a/packages/reshaped/src/components/FormControl/FormControl.module.css +++ b/packages/reshaped/src/components/FormControl/FormControl.module.css @@ -1,10 +1,13 @@ .root { + --rs-form-control-gap: 1; + --rs-form-control-gap-value: calc(var(--rs-form-control-gap) * var(--rs-unit-x1)); + border: none; } .label { display: block; - margin-bottom: var(--rs-unit-x1); + margin-bottom: var(--rs-form-control-gap-value); &:last-child { margin-bottom: 0; @@ -12,10 +15,14 @@ } .caption { - margin-top: var(--rs-unit-x1); + margin-top: var(--rs-form-control-gap-value); display: block; } .caption + .caption { margin-top: 0; } + +.--size-large { + --rs-form-control-gap: 1.5; +} diff --git a/packages/reshaped/src/components/FormControl/FormControl.tsx b/packages/reshaped/src/components/FormControl/FormControl.tsx index b9c034e8..f8075d26 100644 --- a/packages/reshaped/src/components/FormControl/FormControl.tsx +++ b/packages/reshaped/src/components/FormControl/FormControl.tsx @@ -1,15 +1,16 @@ "use client"; -import { useElementId } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; +import useElementId from "@/hooks/useElementId"; import { Provider } from "./FormControl.context"; -import { getCaptionId } from "./FormControl.utilities"; - import type * as T from "./FormControl.types"; +import { getCaptionId } from "./FormControl.utilities"; +import s from "./FormControl.module.css"; const FormControl: React.FC = (props) => { - const { children, id: passedId, required, hasError, group, disabled, size } = props; + const { children, id: passedId, required, hasError, group, disabled, size, gap } = props; const id = useElementId(passedId); const WrapperTagName = group ? "fieldset" : "div"; const [helperRendered, setHelperRendered] = React.useState(false); @@ -31,7 +32,10 @@ const FormControl: React.FC = (props) => { }; return ( - + diff --git a/packages/reshaped/src/components/FormControl/FormControl.types.ts b/packages/reshaped/src/components/FormControl/FormControl.types.ts index b287fd19..95739adc 100644 --- a/packages/reshaped/src/components/FormControl/FormControl.types.ts +++ b/packages/reshaped/src/components/FormControl/FormControl.types.ts @@ -5,6 +5,8 @@ export type Props = { children: React.ReactNode; /** Component size, to be used together with the other form component sizes */ size?: "medium" | "large"; + /** Custom gap between the field and the text labels */ + gap?: number; /** Change component to show an error state and display FormControl.Error */ hasError?: boolean; /** Change component to show a required indicator */ diff --git a/packages/reshaped/src/components/FormControl/FormControlCaption.tsx b/packages/reshaped/src/components/FormControl/FormControlCaption.tsx index db4d2f7e..77d832eb 100644 --- a/packages/reshaped/src/components/FormControl/FormControlCaption.tsx +++ b/packages/reshaped/src/components/FormControl/FormControlCaption.tsx @@ -1,12 +1,10 @@ "use client"; import Text from "@/components/Text"; - import { useFormControlPrivate } from "./FormControl.context"; -import s from "./FormControl.module.css"; -import { getCaptionId } from "./FormControl.utilities"; - import type * as T from "./FormControl.types"; +import { getCaptionId } from "./FormControl.utilities"; +import s from "./FormControl.module.css"; const FormControlCaption: React.FC = (props) => { const { children, variant, disabled } = props; @@ -18,7 +16,7 @@ const FormControlCaption: React.FC = (props) => { return ( = (props) => { const { children } = props; diff --git a/packages/reshaped/src/components/FormControl/FormControlHelper.tsx b/packages/reshaped/src/components/FormControl/FormControlHelper.tsx index e3795caa..d6618372 100644 --- a/packages/reshaped/src/components/FormControl/FormControlHelper.tsx +++ b/packages/reshaped/src/components/FormControl/FormControlHelper.tsx @@ -1,9 +1,8 @@ "use client"; import { useFormControlPrivate } from "./FormControl.context"; -import FormControlCaption from "./FormControlCaption"; - import type * as T from "./FormControl.types"; +import FormControlCaption from "./FormControlCaption"; const FormControlHelper: React.FC = (props) => { const { children } = props; diff --git a/packages/reshaped/src/components/FormControl/FormControlLabel.tsx b/packages/reshaped/src/components/FormControl/FormControlLabel.tsx index e3738ebd..ca0163e0 100644 --- a/packages/reshaped/src/components/FormControl/FormControlLabel.tsx +++ b/packages/reshaped/src/components/FormControl/FormControlLabel.tsx @@ -1,11 +1,9 @@ "use client"; import Text from "@/components/Text"; - import { useFormControlPrivate } from "./FormControl.context"; -import s from "./FormControl.module.css"; - import type * as T from "./FormControl.types"; +import s from "./FormControl.module.css"; const FormControlLabel: React.FC = (props) => { const { children } = props; @@ -15,7 +13,7 @@ const FormControlLabel: React.FC = (props) => { return ( ( - + Label diff --git a/packages/reshaped/src/components/Grid/Grid.tsx b/packages/reshaped/src/components/Grid/Grid.tsx index dd49eb7f..f5779ce0 100644 --- a/packages/reshaped/src/components/Grid/Grid.tsx +++ b/packages/reshaped/src/components/Grid/Grid.tsx @@ -1,11 +1,9 @@ -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; +import { responsivePropDependency, responsiveVariables } from "@/utilities/props"; import { resolveMixin } from "@/styles/mixin"; -import { responsiveVariables, responsivePropDependency } from "@/utilities/props"; - -import s from "./Grid.module.css"; - import type * as T from "./Grid.types"; +import s from "./Grid.module.css"; export const GridItem = ( props: T.ItemProps @@ -21,7 +19,6 @@ export const GridItem = ( children, className, // Using any here to let TS save on type resolving, otherwise TS throws an error due to the type complexity - // eslint-disable-next-line @typescript-eslint/no-explicit-any as: TagName = "div" as any, attributes, } = props; @@ -66,7 +63,6 @@ const Grid = (props: T.Pro height, maxWidth, // Using any here to let TS save on type resolving, otherwise TS throws an error due to the type complexity - // eslint-disable-next-line @typescript-eslint/no-explicit-any as: TagName = "div" as any, attributes, } = props; diff --git a/packages/reshaped/src/components/Grid/Grid.types.ts b/packages/reshaped/src/components/Grid/Grid.types.ts index 5eec9dd4..3aadeaef 100644 --- a/packages/reshaped/src/components/Grid/Grid.types.ts +++ b/packages/reshaped/src/components/Grid/Grid.types.ts @@ -1,8 +1,10 @@ -import type * as TStyles from "@/styles/types"; -import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; import type { Property } from "csstype"; import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + +import type * as TStyles from "@/styles/types"; +import type * as G from "@/types/global"; +import type { Attributes } from "@/types/global"; export type Props = { /** Gap between grid items */ diff --git a/packages/reshaped/src/components/Grid/index.ts b/packages/reshaped/src/components/Grid/index.ts index ff659740..c65905cd 100644 --- a/packages/reshaped/src/components/Grid/index.ts +++ b/packages/reshaped/src/components/Grid/index.ts @@ -7,4 +7,4 @@ const GridRoot = Grid as typeof Grid & { GridRoot.Item = GridItem; export default GridRoot; -export type { Props as GridProps, ItemProps as GridItemProps } from "./Grid.types"; +export type { ItemProps as GridItemProps, Props as GridProps } from "./Grid.types"; diff --git a/packages/reshaped/src/components/Hidden/Hidden.tsx b/packages/reshaped/src/components/Hidden/Hidden.tsx index ee946c33..9ad79a5a 100644 --- a/packages/reshaped/src/components/Hidden/Hidden.tsx +++ b/packages/reshaped/src/components/Hidden/Hidden.tsx @@ -1,10 +1,8 @@ -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; import { responsiveClassNames } from "@/utilities/props"; - -import s from "./Hidden.module.css"; - import type * as T from "./Hidden.types"; +import s from "./Hidden.module.css"; const Hidden: React.FC = (props) => { const { as: TagName = "div", children, visibility, hide } = props; diff --git a/packages/reshaped/src/components/Hidden/Hidden.types.ts b/packages/reshaped/src/components/Hidden/Hidden.types.ts index 8eb70baa..5d9f52f5 100644 --- a/packages/reshaped/src/components/Hidden/Hidden.types.ts +++ b/packages/reshaped/src/components/Hidden/Hidden.types.ts @@ -1,6 +1,7 @@ -import type * as G from "@/types/global"; import type React from "react"; +import type * as G from "@/types/global"; + export type Props = { /** Pick at which viewport sizes to hide the children*/ hide?: G.Responsive; diff --git a/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx b/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx index e4d9e4d4..a7c31810 100644 --- a/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx +++ b/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx @@ -1,15 +1,13 @@ -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; import { useCheckboxGroup } from "@/components/CheckboxGroup"; import { useFormControl } from "@/components/FormControl"; import { useRadioGroup } from "@/components/RadioGroup"; - -import s from "./HiddenInput.module.css"; - import type * as T from "./HiddenInput.types"; +import s from "./HiddenInput.module.css"; const HiddenInput: React.FC = (props) => { - const { type, value, className, onBlur, onFocus, onChange, attributes } = props; + const { type, value, className, onChange, attributes } = props; const rootClassNames = classNames(s.root, className); const checkboxGroup = useCheckboxGroup(); const radioGroup = useRadioGroup(); @@ -45,8 +43,6 @@ const HiddenInput: React.FC = (props) => { defaultChecked={defaultChecked} disabled={disabled} onChange={handleChange} - onFocus={onFocus || attributes?.onFocus} - onBlur={onBlur || attributes?.onBlur} data-rs-hidden-input /> ); diff --git a/packages/reshaped/src/components/HiddenInput/HiddenInput.types.ts b/packages/reshaped/src/components/HiddenInput/HiddenInput.types.ts index 3e90d15e..aec1cbf4 100644 --- a/packages/reshaped/src/components/HiddenInput/HiddenInput.types.ts +++ b/packages/reshaped/src/components/HiddenInput/HiddenInput.types.ts @@ -1,7 +1,7 @@ -import React from "react"; +import type { ClassName } from "@reshaped/utilities"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; +import type { Attributes } from "@/types/global"; export type Props = { /** Name of the input element */ @@ -16,10 +16,6 @@ export type Props = { disabled?: boolean; /** Callback when the input value changes */ onChange?: G.ChangeHandler; - /** Callback when the input or label is focused */ - onFocus?: (e: React.FocusEvent) => void; - /** Callback when the input or label is blurred */ - onBlur?: (e: React.FocusEvent) => void; /** Type of the input element */ type: "checkbox" | "radio"; /** Additional classname for the root element */ diff --git a/packages/reshaped/src/components/HiddenInput/tests/HiddenInput.stories.tsx b/packages/reshaped/src/components/HiddenInput/tests/HiddenInput.stories.tsx index 8f68dba3..8f9dca8b 100644 --- a/packages/reshaped/src/components/HiddenInput/tests/HiddenInput.stories.tsx +++ b/packages/reshaped/src/components/HiddenInput/tests/HiddenInput.stories.tsx @@ -121,47 +121,6 @@ export const disabled: StoryObj = { }, }; -export const onFocus: StoryObj<{ handleFocus: ReturnType }> = { - name: "onFocus", - args: { - handleFocus: fn(), - }, - render: (args) => ( - - ), - play: async ({ canvas, args }) => { - const input = canvas.getByRole("checkbox"); - - await userEvent.click(input); - - expect(args.handleFocus).toHaveBeenCalledTimes(1); - expect(args.handleFocus).toHaveBeenCalledWith(expect.objectContaining({ target: input })); - }, -}; - -export const onBlur: StoryObj<{ handleBlur: ReturnType }> = { - name: "onBlur", - args: { - handleBlur: fn(), - }, - render: (args) => ( -
- - -
- ), - play: async ({ canvas, args }) => { - const input = canvas.getByRole("checkbox"); - const [button] = canvas.getAllByRole("button"); - - await userEvent.click(input); - await userEvent.click(button); - - expect(args.handleBlur).toHaveBeenCalledTimes(1); - expect(args.handleBlur).toHaveBeenCalledWith(expect.objectContaining({ target: input })); - }, -}; - export const className: StoryObj = { name: "className, attributes", render: () => ( diff --git a/packages/reshaped/src/components/HiddenVisually/HiddenVisually.tsx b/packages/reshaped/src/components/HiddenVisually/HiddenVisually.tsx index 884a587b..ffa27c90 100644 --- a/packages/reshaped/src/components/HiddenVisually/HiddenVisually.tsx +++ b/packages/reshaped/src/components/HiddenVisually/HiddenVisually.tsx @@ -1,6 +1,5 @@ -import s from "./HiddenVisually.module.css"; - import type * as T from "./HiddenVisually.types"; +import s from "./HiddenVisually.module.css"; const HiddenVisually: React.FC = (props) => { const { children } = props; diff --git a/packages/reshaped/src/components/Hotkey/Hotkey.module.css b/packages/reshaped/src/components/Hotkey/Hotkey.module.css deleted file mode 100644 index 4590627b..00000000 --- a/packages/reshaped/src/components/Hotkey/Hotkey.module.css +++ /dev/null @@ -1,17 +0,0 @@ -.root { - padding: var(--rs-unit-x1) var(--rs-unit-x2); - min-width: calc(var(--rs-line-height-caption-1) + var(--rs-unit-x1) * 2); - display: inline-flex; - justify-content: center; - vertical-align: top; - background: var(--rs-color-background-neutral-faded); - border-radius: var(--rs-radius-small); - color: var(--rs-color-foreground-neutral); - transition: var(--rs-easing-standard) var(--rs-duration-fast); - transition-property: color, background-color, border-color; -} - -.--active { - background: var(--rs-color-background-neutral); - color: var(--rs-color-on-background-neutral); -} diff --git a/packages/reshaped/src/components/Hotkey/Hotkey.tsx b/packages/reshaped/src/components/Hotkey/Hotkey.tsx deleted file mode 100644 index f00fb9e4..00000000 --- a/packages/reshaped/src/components/Hotkey/Hotkey.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { classNames } from "@reshaped/headless"; - -import Text from "@/components/Text"; - -import s from "./Hotkey.module.css"; - -import type * as T from "./Hotkey.types"; - -const Hotkey: React.FC = (props) => { - const { children, active, className, attributes } = props; - const rootClassNames = classNames(s.root, active && s["--active"], className); - - return ( - - {children} - - ); -}; - -Hotkey.displayName = "Hotkey"; - -export default Hotkey; diff --git a/packages/reshaped/src/components/Hotkey/Hotkey.types.ts b/packages/reshaped/src/components/Hotkey/Hotkey.types.ts deleted file mode 100644 index 06789fd7..00000000 --- a/packages/reshaped/src/components/Hotkey/Hotkey.types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; - -export type Props = { - /** Node for inserting children */ - children: React.ReactNode; - /** Highlight the component, can be used to show when hotkey is pressed */ - active?: boolean; - /** Additional classname for the root element */ - className?: ClassName; - /** Additional attributes for the root element */ - attributes?: Attributes<"span">; -}; diff --git a/packages/reshaped/src/components/Hotkey/index.ts b/packages/reshaped/src/components/Hotkey/index.ts deleted file mode 100644 index b982f139..00000000 --- a/packages/reshaped/src/components/Hotkey/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./Hotkey"; -export type { Props as HotkeyProps } from "./Hotkey.types"; diff --git a/packages/reshaped/src/components/Hotkey/tests/Hotkey.stories.tsx b/packages/reshaped/src/components/Hotkey/tests/Hotkey.stories.tsx deleted file mode 100644 index 345df398..00000000 --- a/packages/reshaped/src/components/Hotkey/tests/Hotkey.stories.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useHotkeys } from "@reshaped/headless"; -import { StoryObj } from "@storybook/react-vite"; -import { expect } from "storybook/test"; - -import TextField from "@/components/TextField"; -import View from "@/components/View"; -import { Example } from "@/utilities/storybook"; - -import Hotkey from "../Hotkey"; - -export default { - title: "Components/Hotkey", - component: Hotkey, - parameters: { - iframe: { - url: "https://reshaped.so/docs/components/hotkey", - }, - }, -}; - -const Demo = () => { - const { checkHotkeyState } = useHotkeys({ - "Meta+v": () => { - console.log("meta v"); - }, - "Mod+v": () => { - console.log("mod v"); - }, - }); - - return ⌘K; -}; - -export const base = () => ( - - - - - - ⌘K - - - - } - inputAttributes={{ "aria-label": "hotkey test" }} - /> - - - -); - -export const className: StoryObj = { - name: "className, attributes", - render: () => ( -
- - ⌘K - -
- ), - play: async ({ canvas }) => { - const root = canvas.getByTestId("root").firstChild as HTMLElement; - - expect(root).toHaveClass("test-classname"); - expect(root).toHaveAttribute("id", "test-id"); - expect(root?.tagName).toBe("KBD"); - }, -}; diff --git a/packages/reshaped/src/components/Icon/Icon.tsx b/packages/reshaped/src/components/Icon/Icon.tsx index a556122f..6d543f72 100644 --- a/packages/reshaped/src/components/Icon/Icon.tsx +++ b/packages/reshaped/src/components/Icon/Icon.tsx @@ -1,11 +1,9 @@ -import { classNames } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; import { resolveMixin } from "@/styles/mixin"; - -import s from "./Icon.module.css"; - import type * as T from "./Icon.types"; +import s from "./Icon.module.css"; const Icon: React.FC = (props) => { const { svg: Component, className, color, size = "1em", autoWidth, attributes } = props; diff --git a/packages/reshaped/src/components/Icon/Icon.types.ts b/packages/reshaped/src/components/Icon/Icon.types.ts index f980472a..eb95c6ec 100644 --- a/packages/reshaped/src/components/Icon/Icon.types.ts +++ b/packages/reshaped/src/components/Icon/Icon.types.ts @@ -1,6 +1,8 @@ -import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + +import type * as G from "@/types/global"; +import type { Attributes } from "@/types/global"; export type Props = { /** Icon svg component or node */ diff --git a/packages/reshaped/src/components/Icon/tests/Icon.stories.tsx b/packages/reshaped/src/components/Icon/tests/Icon.stories.tsx index 32ec688a..30d497af 100644 --- a/packages/reshaped/src/components/Icon/tests/Icon.stories.tsx +++ b/packages/reshaped/src/components/Icon/tests/Icon.stories.tsx @@ -4,9 +4,9 @@ import { expect } from "storybook/test"; import Icon from "@/components/Icon"; import Text from "@/components/Text"; import View from "@/components/View"; +import { Example } from "@/utilities/storybook"; import IconMic from "@/icons/Mic"; import IconZap from "@/icons/Zap"; -import { Example } from "@/utilities/storybook"; export default { title: "Utility components/Icon", diff --git a/packages/reshaped/src/components/Image/Image.tsx b/packages/reshaped/src/components/Image/Image.tsx index 44e1bef1..35b47a5a 100644 --- a/packages/reshaped/src/components/Image/Image.tsx +++ b/packages/reshaped/src/components/Image/Image.tsx @@ -1,12 +1,11 @@ "use client"; -import { classNames } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; import { resolveMixin } from "@/styles/mixin"; - -import s from "./Image.module.css"; import * as T from "./Image.types"; +import s from "./Image.module.css"; const Image: React.FC = (props) => { const { @@ -72,7 +71,7 @@ const Image: React.FC = (props) => { style, }; - // eslint-disable-next-line jsx-a11y/alt-text + // oxlint-disable-next-line jsx_a11y/alt-text return renderImage ? renderImage(imageAttributes) : ; } diff --git a/packages/reshaped/src/components/Image/Image.types.ts b/packages/reshaped/src/components/Image/Image.types.ts index 56de7fbe..376e3711 100644 --- a/packages/reshaped/src/components/Image/Image.types.ts +++ b/packages/reshaped/src/components/Image/Image.types.ts @@ -1,7 +1,9 @@ +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type * as TStyles from "@/styles/types"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; export type Props = { /** Image URL */ diff --git a/packages/reshaped/src/components/Image/tests/Image.stories.tsx b/packages/reshaped/src/components/Image/tests/Image.stories.tsx index 0507f341..37bb3844 100644 --- a/packages/reshaped/src/components/Image/tests/Image.stories.tsx +++ b/packages/reshaped/src/components/Image/tests/Image.stories.tsx @@ -3,9 +3,8 @@ import { expect, fn, Mock, waitFor } from "storybook/test"; import Icon from "@/components/Icon"; import View from "@/components/View"; -import IconZap from "@/icons/Zap"; import { Example } from "@/utilities/storybook"; - +import IconZap from "@/icons/Zap"; import Image from "../Image"; export default { @@ -206,7 +205,7 @@ export const renderImage: StoryObj = { Amsterdam canal } />
@@ -215,7 +214,7 @@ export const renderImage: StoryObj = { src="error" fallback={imgUrl} alt="Amsterdam canal 2" - // eslint-disable-next-line jsx-a11y/alt-text + // oxlint-disable-next-line jsx_a11y/alt-text renderImage={(attributes) => } /> diff --git a/packages/reshaped/src/components/Link/Link.module.css b/packages/reshaped/src/components/Link/Link.module.css index 797f6be6..a6778b66 100644 --- a/packages/reshaped/src/components/Link/Link.module.css +++ b/packages/reshaped/src/components/Link/Link.module.css @@ -57,5 +57,5 @@ .root.--with-icon { display: inline-flex; align-items: center; - gap: calc(1em / 3.5); + gap: round(down, calc(1em / 2.5), var(--rs-unit-x1)); } diff --git a/packages/reshaped/src/components/Link/Link.tsx b/packages/reshaped/src/components/Link/Link.tsx index 95450a4e..1f57f000 100644 --- a/packages/reshaped/src/components/Link/Link.tsx +++ b/packages/reshaped/src/components/Link/Link.tsx @@ -1,12 +1,10 @@ -import { classNames } from "@reshaped/headless"; import { forwardRef } from "react"; +import { classNames } from "@reshaped/utilities"; import Actionable, { type ActionableRef } from "@/components/Actionable"; import Icon from "@/components/Icon"; - -import s from "./Link.module.css"; - import type * as T from "./Link.types"; +import s from "./Link.module.css"; const Link = forwardRef((props, ref) => { const { diff --git a/packages/reshaped/src/components/Link/tests/Link.stories.tsx b/packages/reshaped/src/components/Link/tests/Link.stories.tsx index 8bc9d2ba..2c558ee9 100644 --- a/packages/reshaped/src/components/Link/tests/Link.stories.tsx +++ b/packages/reshaped/src/components/Link/tests/Link.stories.tsx @@ -3,8 +3,8 @@ import { expect, fn, userEvent } from "storybook/test"; import Link from "@/components/Link"; import Text from "@/components/Text"; -import IconZap from "@/icons/Zap"; import { Example } from "@/utilities/storybook"; +import IconZap from "@/icons/Zap"; export default { title: "Components/Link", @@ -39,19 +39,29 @@ export const color = { render: () => ( - Link + {}}> + Link + - Link + {}}> + Link + - Link + {}}> + Link + - Link + {}}> + Link + - Link + {}}> + Link + ), @@ -62,10 +72,12 @@ export const icon = { render: () => ( - Link + {}}> + Link + - + {}}> Link @@ -73,7 +85,7 @@ export const icon = { title={["icon, variant: underline", "should inherit display-1 size from the parent"]} > - + {}}> Instant delivery diff --git a/packages/reshaped/src/components/Loader/Loader.module.css b/packages/reshaped/src/components/Loader/Loader.module.css index 5e37e96c..eec395fb 100644 --- a/packages/reshaped/src/components/Loader/Loader.module.css +++ b/packages/reshaped/src/components/Loader/Loader.module.css @@ -1,21 +1,7 @@ -@keyframes rs-reshaped-loader { - 0% { - transform: rotate(0deg); - } - - 50% { - transform: rotate(540deg); - } - - 100% { - transform: rotate(1080deg); - } -} - .root { position: relative; display: block; - animation: rs-reshaped-loader 2.2s infinite cubic-bezier(0.445, 0.05, 0.55, 0.95); + animation: rs-reshaped-loader 2.2s infinite cubic-bezier(0.6, 0.3, 0.3, 0.9); width: var(--rs-loader-size); height: var(--rs-loader-size); } @@ -92,3 +78,17 @@ --rs-loader-stroke: 5px; } } + +@keyframes rs-reshaped-loader { + 0% { + transform: rotate(0deg); + } + + 50% { + transform: rotate(540deg); + } + + 100% { + transform: rotate(1080deg); + } +} diff --git a/packages/reshaped/src/components/Loader/Loader.tsx b/packages/reshaped/src/components/Loader/Loader.tsx index db103ab5..8daa6fdc 100644 --- a/packages/reshaped/src/components/Loader/Loader.tsx +++ b/packages/reshaped/src/components/Loader/Loader.tsx @@ -1,10 +1,8 @@ -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; import { responsiveClassNames } from "@/utilities/props"; - -import s from "./Loader.module.css"; - import type * as T from "./Loader.types"; +import s from "./Loader.module.css"; const Loader: React.FC = (props) => { const { size = "small", color = "primary", className, attributes } = props; diff --git a/packages/reshaped/src/components/Loader/Loader.types.ts b/packages/reshaped/src/components/Loader/Loader.types.ts index 5b2c1b7b..b4d76cec 100644 --- a/packages/reshaped/src/components/Loader/Loader.types.ts +++ b/packages/reshaped/src/components/Loader/Loader.types.ts @@ -1,5 +1,7 @@ +import type { ClassName } from "@reshaped/utilities"; + import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; +import type { Attributes } from "@/types/global"; export type Props = { /** Component size */ diff --git a/packages/reshaped/src/components/Loader/tests/Loader.stories.tsx b/packages/reshaped/src/components/Loader/tests/Loader.stories.tsx index f14ee377..b284d9ad 100644 --- a/packages/reshaped/src/components/Loader/tests/Loader.stories.tsx +++ b/packages/reshaped/src/components/Loader/tests/Loader.stories.tsx @@ -19,12 +19,12 @@ export const size = { render: () => { return ( - - - + + + diff --git a/packages/reshaped/src/components/MenuItem/MenuItem.module.css b/packages/reshaped/src/components/MenuItem/MenuItem.module.css index 6ada7c3d..96c8ed8a 100644 --- a/packages/reshaped/src/components/MenuItem/MenuItem.module.css +++ b/packages/reshaped/src/components/MenuItem/MenuItem.module.css @@ -2,12 +2,13 @@ /* Higher priority as a workaround for Tailwind button styles reset */ button.root { display: block; - transition: background-color var(--rs-duration-fast) var(--rs-easing-standard); + transition: background-color var(--rs-duration-rapid) var(--rs-easing-standard); padding: var(--rs-p-v) var(--rs-p-h); font-family: var(--rs-font-family-body); font-weight: var(--rs-font-weight-medium); color: var(--rs-menu-item-color); background-color: var(--rs-menu-item-bg-color); + border-radius: var(--rs-radius-medium); &[role="option"] { transition: none; @@ -15,7 +16,7 @@ button.root { [data-rs-keyboard] &[role="option"]:focus { box-shadow: none; - background-color: rgba(var(--rs-color-rgb-background-neutral), 32%); + background-color: var(--rs-color-background-neutral-highlighted-faded); } } @@ -25,39 +26,29 @@ button.root { .icon, .content { - transition: var(--rs-duration-fast) var(--rs-easing-standard); + transition: var(--rs-duration-rapid) var(--rs-easing-standard); transition-property: color; } -@responsive .--rounded-corners { - @value true { - border-radius: var(--rs-menu-item-radius); - } - - @value false { - border-radius: 0; - } -} - @responsive .--size { @value small { --rs-p-v: var(--rs-unit-x1); - --rs-p-h: var(--rs-unit-x2); + --rs-p-h: var(--rs-unit-x1-5); --rs-menu-item-radius: var(--rs-radius-small); - font-size: var(--rs-font-size-body-3); - line-height: var(--rs-line-height-body-3); - letter-spacing: var(--rs-letter-spacing-body-3); + font-size: var(--rs-font-size-body-2); + line-height: var(--rs-line-height-body-2); + letter-spacing: var(--rs-letter-spacing-body-2); } @value medium { --rs-p-v: var(--rs-unit-x2); --rs-p-h: var(--rs-unit-x3); - --rs-menu-item-radius: var(--rs-radius-small); + --rs-menu-item-radius: var(--rs-radius-medium); - font-size: var(--rs-font-size-body-3); - line-height: var(--rs-line-height-body-3); - letter-spacing: var(--rs-letter-spacing-body-3); + font-size: var(--rs-font-size-body-2); + line-height: var(--rs-line-height-body-2); + letter-spacing: var(--rs-letter-spacing-body-2); } @value large { @@ -65,9 +56,9 @@ button.root { --rs-p-h: var(--rs-unit-x4); --rs-menu-item-radius: var(--rs-radius-medium); - font-size: var(--rs-font-size-body-2); - line-height: var(--rs-line-height-body-2); - letter-spacing: var(--rs-letter-spacing-body-2); + font-size: var(--rs-font-size-body-1); + line-height: var(--rs-line-height-body-1); + letter-spacing: var(--rs-letter-spacing-body-1); } } @@ -77,12 +68,12 @@ button.root { &.--selected, &[data-rs-focus], &.--highlighted { - --rs-menu-item-bg-color: rgba(var(--rs-color-rgb-background-neutral), 32%); + --rs-menu-item-bg-color: var(--rs-color-background-neutral-highlighted-faded); } @media (hover: hover) and (pointer: fine) { &:hover { - --rs-menu-item-bg-color: rgba(var(--rs-color-rgb-background-neutral), 32%); + --rs-menu-item-bg-color: var(--rs-color-background-neutral-highlighted-faded); } } } @@ -93,12 +84,12 @@ button.root { &.--selected, &[data-rs-focus], &.--highlighted { - --rs-menu-item-bg-color: rgba(var(--rs-color-rgb-background-critical), 12%); + --rs-menu-item-bg-color: var(--rs-color-background-critical-highlighted-faded); } @media (hover: hover) and (pointer: fine) { &:hover { - --rs-menu-item-bg-color: rgba(var(--rs-color-rgb-background-critical), 12%); + --rs-menu-item-bg-color: var(--rs-color-background-critical-highlighted-faded); } } } @@ -108,18 +99,18 @@ button.root { &[data-rs-focus], &.--highlighted { - --rs-menu-item-bg-color: rgba(var(--rs-color-rgb-background-neutral), 32%); + --rs-menu-item-bg-color: var(--rs-color-background-neutral-highlighted-faded); } @media (hover: hover) and (pointer: fine) { &:hover { - --rs-menu-item-bg-color: rgba(var(--rs-color-rgb-background-neutral), 32%); + --rs-menu-item-bg-color: var(--rs-color-background-neutral-highlighted-faded); } } &.--selected, &.--selected:hover { - --rs-menu-item-bg-color: rgba(var(--rs-color-rgb-background-primary), 12%); + --rs-menu-item-bg-color: var(--rs-color-background-primary-highlighted-faded); --rs-menu-item-color: var(--rs-color-foreground-primary); --rs-menu-item-icon-color: var(--rs-color-foreground-primary); } diff --git a/packages/reshaped/src/components/MenuItem/MenuItem.tsx b/packages/reshaped/src/components/MenuItem/MenuItem.tsx index 2bb691c4..f903905e 100644 --- a/packages/reshaped/src/components/MenuItem/MenuItem.tsx +++ b/packages/reshaped/src/components/MenuItem/MenuItem.tsx @@ -1,14 +1,12 @@ -import { classNames } from "@reshaped/headless"; import { forwardRef } from "react"; +import { classNames } from "@reshaped/utilities"; import Actionable, { type ActionableRef } from "@/components/Actionable"; import Icon from "@/components/Icon"; import View from "@/components/View"; import { responsiveClassNames, responsivePropDependency } from "@/utilities/props"; - -import s from "./MenuItem.module.css"; - import type * as T from "./MenuItem.types"; +import s from "./MenuItem.module.css"; const MenuItem = forwardRef((props, ref) => { const { @@ -23,7 +21,6 @@ const MenuItem = forwardRef((props, ref) => { onClick, href, size = "medium", - roundedCorners, stopPropagation, as, render, @@ -34,7 +31,6 @@ const MenuItem = forwardRef((props, ref) => { s.root, className, responsiveClassNames(s, "--size", size), - responsiveClassNames(s, "--rounded-corners", roundedCorners), color && s[`--color-${color}`], selected && s["--selected"], disabled && s["--disabled"], diff --git a/packages/reshaped/src/components/MenuItem/MenuItem.types.ts b/packages/reshaped/src/components/MenuItem/MenuItem.types.ts index 6c4da06a..7a4d757c 100644 --- a/packages/reshaped/src/components/MenuItem/MenuItem.types.ts +++ b/packages/reshaped/src/components/MenuItem/MenuItem.types.ts @@ -1,8 +1,10 @@ +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type { ActionableProps } from "@/components/Actionable"; import type { IconProps } from "@/components/Icon"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; export type Size = "small" | "medium" | "large"; diff --git a/packages/reshaped/src/components/MenuItem/MenuItemAligner.tsx b/packages/reshaped/src/components/MenuItem/MenuItemAligner.tsx index 45b80984..1cb5ec25 100644 --- a/packages/reshaped/src/components/MenuItem/MenuItemAligner.tsx +++ b/packages/reshaped/src/components/MenuItem/MenuItemAligner.tsx @@ -1,5 +1,4 @@ import Aligner, { type AlignerProps } from "@/components/_private/Aligner"; - import s from "./MenuItem.module.css"; const MenuItemAligner: React.FC = (props) => { diff --git a/packages/reshaped/src/components/MenuItem/tests/MenuItem.stories.tsx b/packages/reshaped/src/components/MenuItem/tests/MenuItem.stories.tsx index 862f3f34..b5b7aee7 100644 --- a/packages/reshaped/src/components/MenuItem/tests/MenuItem.stories.tsx +++ b/packages/reshaped/src/components/MenuItem/tests/MenuItem.stories.tsx @@ -1,12 +1,12 @@ import { StoryObj } from "@storybook/react-vite"; import { expect, fn, userEvent } from "storybook/test"; -import Hotkey from "@/components/Hotkey"; +import Badge from "@/components/Badge"; import MenuItem from "@/components/MenuItem"; import Text from "@/components/Text"; import View from "@/components/View"; -import IconZap from "@/icons/Zap"; import { Example, Placeholder } from "@/utilities/storybook"; +import IconZap from "@/icons/Zap"; export default { title: "Components/MenuItem", @@ -23,17 +23,27 @@ export const size = { render: () => ( - {}} endSlot={⌘K}> + {}} + endSlot={12} + > Menu item - {}}> + {}} endSlot={12}> Menu item - {}}> + {}} + endSlot={12} + > Menu item @@ -51,17 +61,17 @@ export const color = { render: () => ( - + {}}> Menu item - + {}}> Menu item - + {}}> Menu item @@ -74,36 +84,17 @@ export const selected = { render: () => ( - + {}}> Menu item - + {}}> Menu item - - Menu item - - - - ), -}; - -export const roundedCorners = { - name: "roundedCorners", - render: () => ( - - - - Menu item - - - - - + {}}> Menu item @@ -116,7 +107,12 @@ export const slots = { render: () => ( - } endSlot={} selected> + } + endSlot={} + selected + onClick={() => {}} + > Menu item @@ -143,7 +139,9 @@ export const aligner = { Heading - Menu item + {}}> + Menu item + @@ -152,7 +150,7 @@ export const aligner = { Heading - + {}}> Menu item diff --git a/packages/reshaped/src/components/Modal/Modal.module.css b/packages/reshaped/src/components/Modal/Modal.module.css index ba5d31e9..094dbccc 100644 --- a/packages/reshaped/src/components/Modal/Modal.module.css +++ b/packages/reshaped/src/components/Modal/Modal.module.css @@ -6,12 +6,13 @@ transition-property: transform, opacity; background: var(--rs-color-background-elevation-overlay); color: var(--rs-color-foreground-neutral); - box-shadow: var(--rs-shadow-raised); + box-shadow: var(--rs-shadow-overlay); will-change: transform, opacity; outline: none; + overscroll-behavior: none; [data-rs-keyboard] &:focus { - box-shadow: var(--rs-shadow-focus); + outline: var(--rs-outline); } &.--contained { @@ -37,12 +38,13 @@ position: relative; border-radius: var(--rs-radius-large); overflow: hidden; + overscroll-behavior: auto; opacity: 0; transform: translate(0, var(--rs-unit-x4)); &.--active, [dir="rtl"] &.--active { - transform: translate(0, 0) !important; + transform: translate(0, 0); opacity: 1; } } @@ -62,11 +64,12 @@ position: absolute; border-radius: var(--rs-radius-large) var(--rs-radius-large) 0 0; overflow: auto; + overscroll-behavior: none; opacity: 1; &.--active, [dir="rtl"] &.--active { - transform: translate(0, max(var(--rs-modal-drag, 0px), 0px)) !important; + transform: translate(0, max(var(--rs-modal-drag, 0px), 0px)); } } @@ -85,6 +88,7 @@ position: absolute; border-radius: 0; overflow: auto; + overscroll-behavior: none; opacity: 1; [dir="rtl"] & { @@ -93,7 +97,7 @@ &.--active, [dir="rtl"] &.--active { - transform: translate(min(var(--rs-modal-drag, 0px), 0px), 0) !important; + transform: translate(min(var(--rs-modal-drag, 0px), 0px), 0); } } @@ -112,6 +116,7 @@ position: absolute; border-radius: 0; overflow: auto; + overscroll-behavior: none; opacity: 1; [dir="rtl"] & { @@ -120,7 +125,7 @@ &.--active, [dir="rtl"] &.--active { - transform: translate(max(var(--rs-modal-drag, 0px), 0px), 0) !important; + transform: translate(max(var(--rs-modal-drag, 0px), 0px), 0); } } @@ -138,11 +143,12 @@ position: absolute; border-radius: 0; overflow: auto; + overscroll-behavior: none; opacity: 0; &.--active, [dir="rtl"] &.--active { - transform: translate(0, 0) !important; + transform: translate(0, 0); opacity: 1; } } diff --git a/packages/reshaped/src/components/Modal/Modal.tsx b/packages/reshaped/src/components/Modal/Modal.tsx index f1acd0aa..b432fe54 100644 --- a/packages/reshaped/src/components/Modal/Modal.tsx +++ b/packages/reshaped/src/components/Modal/Modal.tsx @@ -1,65 +1,75 @@ "use client"; -import { classNames, useHandlerRef, useElementId } from "@reshaped/headless"; -import { enableScroll, disableScroll } from "@reshaped/headless/internal"; -import React from "react"; - -import Overlay from "@/components/Overlay"; +import { + useCallback, + createContext, + useContext, + useEffect, + useMemo, + useState, + useRef, + type FC, +} from "react"; +import { classNames } from "@reshaped/utilities"; +import { enableScroll, disableScroll } from "@reshaped/utilities/internal"; + +import Overlay, { type OverlayInstance } from "@/components/Overlay"; import Text from "@/components/Text"; +import useElementId from "@/hooks/useElementId"; +import useHandlerRef from "@/hooks/useHandlerRef"; import useResponsiveClientValue from "@/hooks/useResponsiveClientValue"; +import { responsiveClassNames, responsiveVariables } from "@/utilities/props"; import { resolveMixin } from "@/styles/mixin"; -import { responsiveVariables, responsiveClassNames } from "@/utilities/props"; - -import s from "./Modal.module.css"; - import type * as T from "./Modal.types"; +import s from "./Modal.module.css"; const DRAG_THRESHOLD = 32; const DRAG_OPPOSITE_THRESHOLD = 100; const DRAG_EDGE_BOUNDARY = 32; +const DRAG_SCROLL_LOCK_THRESHOLD = 8; -const Context = React.createContext({ +const Context = createContext({ id: "", titleMounted: false, setTitleMounted: () => {}, subtitleMounted: false, setSubtitleMounted: () => {}, }); -const useModal = () => React.useContext(Context); +const useModal = () => useContext(Context); -export const ModalTitle: React.FC = (props) => { +export const ModalTitle: FC = (props) => { const { children } = props; const { id, setTitleMounted } = useModal(); - React.useEffect(() => { + useEffect(() => { setTitleMounted(true); return () => setTitleMounted(false); }, [setTitleMounted]); return ( - + {children} ); }; -export const ModalSubtitle: React.FC = (props) => { +export const ModalSubtitle: FC = (props) => { const { children } = props; const { id, setSubtitleMounted } = useModal(); - React.useEffect(() => { + useEffect(() => { setSubtitleMounted(true); return () => setSubtitleMounted(false); }, [setSubtitleMounted]); return ( - + {children} ); }; -const Modal: React.FC = (props) => { +const Modal: FC = (props) => { const { children, onClose, @@ -86,21 +96,45 @@ const Modal: React.FC = (props) => { const onCloseRef = useHandlerRef(onClose); const id = useElementId(); const clientPosition = useResponsiveClientValue(position)!; - const [titleMounted, setTitleMounted] = React.useState(false); - const [subtitleMounted, setSubtitleMounted] = React.useState(false); - const [dragging, setDragging] = React.useState(false); - const internalRootRef = React.useRef(null); + const [titleMounted, setTitleMounted] = useState(false); + const [subtitleMounted, setSubtitleMounted] = useState(false); + const [dragging, setDragging] = useState(false); + const internalRootRef = useRef(null); const rootRef = attributes?.ref || internalRootRef; - const dragStartCoordinatesRef = React.useRef({ x: 0, y: 0 }); - const dragLastCoordinateRef = React.useRef(0); - const dragDistanceRef = React.useRef(0); - const dragDirectionRef = React.useRef(0); - const [dragDistance, setDragDistance] = React.useState(0); - const [hideProgress, setHideProgress] = React.useState(0); + const dragStartCoordinatesRef = useRef({ x: 0, y: 0 }); + const dragLastCoordinateRef = useRef(0); + const dragDistanceRef = useRef(0); + const dragFrameRef = useRef(null); + const dragPendingDistanceRef = useRef(0); + const dragSizeRef = useRef(1); + const overlayRef = useRef(null); + const dragDirectionRef = useRef(0); + const dragShouldCloseRef = useRef(false); const mixinStyles = resolveMixin({ padding }); const shouldBeContained = containerRef && contained !== false; - const value = React.useMemo( + const setDragStyles = useCallback( + (distance: number) => { + const rootEl = rootRef.current; + if (!rootEl) return; + + const dragOffset = + Math.abs(distance) < DRAG_THRESHOLD + ? 0 + : distance + DRAG_THRESHOLD * (clientPosition === "start" ? 1 : -1); + + rootEl.style.setProperty("--rs-modal-drag", `${dragOffset}px`); + + if (!transparentOverlay && overlayRef.current) { + const progress = Math.min(1, Math.abs(distance) / dragSizeRef.current); + const opacity = Math.max(0, 1 - progress * 0.5); + overlayRef.current.setOpacity(opacity); + } + }, + [clientPosition, overlayRef, transparentOverlay, rootRef] + ); + + const value = useMemo( () => ({ titleMounted, setTitleMounted, @@ -111,15 +145,27 @@ const Modal: React.FC = (props) => { [id, subtitleMounted, titleMounted] ); - const resetDragData = () => { + const resetDragData = useCallback(() => { + dragDistanceRef.current = 0; + dragPendingDistanceRef.current = 0; dragStartCoordinatesRef.current = { x: 0, y: 0 }; dragLastCoordinateRef.current = 0; dragDirectionRef.current = 0; - setDragDistance(0); - }; + dragShouldCloseRef.current = false; + setDragStyles(0); + }, [setDragStyles]); + + const unlockDragScroll = useCallback(() => { + rootRef.current?.style.removeProperty("overflow"); + }, [rootRef]); + + const lockDragScroll = useCallback(() => { + rootRef.current?.style.setProperty("overflow", "hidden"); + }, [rootRef]); const handleDragStart = (e: React.TouchEvent) => { if (disableSwipeGesture) return; + dragShouldCloseRef.current = false; // Prevent swipe to close from happening when user is working with text selection if (window.getSelection()?.toString()) return; @@ -139,6 +185,11 @@ const Modal: React.FC = (props) => { // Prevent the drag handling when browser is trying to navigate to a previous page if (clientPosition === "start" && e.targetTouches[0].clientX < DRAG_EDGE_BOUNDARY) return; + if (rootEl) { + const isInline = ["start", "end"].includes(clientPosition); + dragSizeRef.current = Math.max(1, isInline ? rootEl.clientWidth : rootEl.clientHeight); + } + if (containerRef?.current) disableScroll(); setDragging(true); }; @@ -150,21 +201,23 @@ const Modal: React.FC = (props) => { if (e.currentTarget !== e.target) return; resetDragData(); + unlockDragScroll(); }; - React.useEffect(() => { + useEffect(() => { if (!dragging) return; const handleDragEnd = () => { - if (containerRef?.current) enableScroll(); - setDragging(false); - - // Close only when dragging in the closing direction - // Changing to a different direction will keep the modal opened const shouldClose = clientPosition === "start" ? dragDirectionRef.current < 0 : dragDirectionRef.current > 0; + dragShouldCloseRef.current = + Math.abs(dragDistanceRef.current) > DRAG_THRESHOLD && shouldClose; - if (Math.abs(dragDistanceRef.current) > DRAG_THRESHOLD && shouldClose) { + if (containerRef?.current) enableScroll(); + if (!dragShouldCloseRef.current) unlockDragScroll(); + setDragging(false); + + if (dragShouldCloseRef.current) { onCloseRef.current?.({ reason: "drag" }); } else { resetDragData(); @@ -204,46 +257,63 @@ const Modal: React.FC = (props) => { dragDirectionRef.current = coordinate[key] - dragLastCoordinateRef.current; dragLastCoordinateRef.current = coordinate[key]; - setDragDistance((prev) => + const isClosingDirection = + clientPosition === "start" ? dragDirectionRef.current < 0 : dragDirectionRef.current > 0; + + if (next > DRAG_SCROLL_LOCK_THRESHOLD && isClosingDirection) { + lockDragScroll(); + } + + dragDistanceRef.current = clientPosition === "start" - ? Math.min(0, prev + dragDirectionRef.current) - : Math.max(0, prev + dragDirectionRef.current) - ); + ? Math.min(0, dragDistanceRef.current + dragDirectionRef.current) + : Math.max(0, dragDistanceRef.current + dragDirectionRef.current); + + dragPendingDistanceRef.current = dragDistanceRef.current; + + if (dragFrameRef.current === null) { + dragFrameRef.current = window.requestAnimationFrame(() => { + setDragStyles(dragPendingDistanceRef.current); + dragFrameRef.current = null; + }); + } }; document.addEventListener("touchmove", handleDrag, { passive: true }); document.addEventListener("touchend", handleDragEnd, { passive: true }); return () => { + if (dragFrameRef.current !== null) { + window.cancelAnimationFrame(dragFrameRef.current); + dragFrameRef.current = null; + } + if (!dragShouldCloseRef.current) unlockDragScroll(); document.removeEventListener("touchmove", handleDrag); document.removeEventListener("touchend", handleDragEnd); }; - }, [dragging, clientPosition, onCloseRef, position, rootRef, containerRef]); - - // Syncing distance to the ref to avoid having a dependency on dragDistance in handleDragEnd - React.useEffect(() => { - const rootEl = rootRef.current; - - if (!rootEl || !clientPosition) return; - - const isInline = ["start", "end"].includes(clientPosition); - - const size = isInline ? rootEl.clientWidth : rootEl.clientHeight; - const progress = Math.abs(dragDistance) / size; - - setHideProgress(progress / 2); - dragDistanceRef.current = dragDistance; - }, [dragDistance, clientPosition, rootRef]); + }, [ + dragging, + clientPosition, + onCloseRef, + position, + rootRef, + containerRef, + setDragStyles, + resetDragData, + lockDragScroll, + unlockDragScroll, + ]); return ( = (props) => { { ...mixinStyles.variables, ...responsiveVariables("--rs-modal-size", size), - "--rs-modal-drag": - Math.abs(dragDistance) < DRAG_THRESHOLD - ? "0px" - : `${ - dragDistance + DRAG_THRESHOLD * (clientPosition === "start" ? 1 : -1) - }px`, + "--rs-modal-drag": "0px", } as React.CSSProperties } aria-labelledby={titleMounted ? `${id}-title` : undefined} diff --git a/packages/reshaped/src/components/Modal/Modal.types.ts b/packages/reshaped/src/components/Modal/Modal.types.ts index f2882231..029b926c 100644 --- a/packages/reshaped/src/components/Modal/Modal.types.ts +++ b/packages/reshaped/src/components/Modal/Modal.types.ts @@ -1,7 +1,9 @@ -import type { OverlayProps, OverlayCloseReason } from "@/components/Overlay"; -import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + +import type { OverlayCloseReason, OverlayProps } from "@/components/Overlay"; +import type * as G from "@/types/global"; +import type { Attributes } from "@/types/global"; export type Context = { id: string; diff --git a/packages/reshaped/src/components/Modal/index.ts b/packages/reshaped/src/components/Modal/index.ts index f931d85e..90f4990f 100644 --- a/packages/reshaped/src/components/Modal/index.ts +++ b/packages/reshaped/src/components/Modal/index.ts @@ -1,4 +1,4 @@ -import Modal, { ModalTitle, ModalSubtitle } from "./Modal"; +import Modal, { ModalSubtitle, ModalTitle } from "./Modal"; const ModalRoot = Modal as typeof Modal & { Title: typeof ModalTitle; diff --git a/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx b/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx index e0206e5e..ff190128 100644 --- a/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx +++ b/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx @@ -1,4 +1,3 @@ -import { useToggle } from "@reshaped/headless"; import { StoryObj } from "@storybook/react-vite"; import React from "react"; import { expect, fn, userEvent, waitFor, within } from "storybook/test"; @@ -11,6 +10,7 @@ import Radio from "@/components/Radio"; import Switch from "@/components/Switch"; import TextField from "@/components/TextField"; import View from "@/components/View"; +import useToggle from "@/hooks/useToggle"; import { sleep } from "@/utilities/helpers"; import { Example, Placeholder } from "@/utilities/storybook"; @@ -435,7 +435,7 @@ export const edgeCases = { - {/* eslint-disable-next-line jsx-a11y/no-autofocus */} + {/* oxlint-disable-next-line jsx_a11y/no-autofocus */} diff --git a/packages/reshaped/src/components/NumberField/NumberField.module.css b/packages/reshaped/src/components/NumberField/NumberField.module.css index 30bce80c..7084ff98 100644 --- a/packages/reshaped/src/components/NumberField/NumberField.module.css +++ b/packages/reshaped/src/components/NumberField/NumberField.module.css @@ -12,7 +12,7 @@ display: flex; flex-direction: column; position: absolute; - inset-block: 0; + inset-block: 1px; inset-inline-end: 0; transition: border-color var(--rs-duration-fast) var(--rs-easing-standard); @@ -99,7 +99,7 @@ @media (hover: hover) and (pointer: fine) { &:not([aria-disabled]):hover { - background: rgba(var(--rs-color-rgb-background-neutral), 32%); + background: var(--rs-color-background-neutral-highlighted-faded); } } } diff --git a/packages/reshaped/src/components/NumberField/NumberField.tsx b/packages/reshaped/src/components/NumberField/NumberField.tsx index 37a309e0..85c630b3 100644 --- a/packages/reshaped/src/components/NumberField/NumberField.tsx +++ b/packages/reshaped/src/components/NumberField/NumberField.tsx @@ -1,8 +1,7 @@ +import type * as T from "./NumberField.types"; import NumberFieldControlled from "./NumberFieldControlled"; import NumberFieldUncontrolled from "./NumberFieldUncontrolled"; -import type * as T from "./NumberField.types"; - const NumberField: React.FC = (props) => { const { value } = props; diff --git a/packages/reshaped/src/components/NumberField/NumberFieldControlled.tsx b/packages/reshaped/src/components/NumberField/NumberFieldControlled.tsx index 6bfbf3b0..5fb46fd6 100644 --- a/packages/reshaped/src/components/NumberField/NumberFieldControlled.tsx +++ b/packages/reshaped/src/components/NumberField/NumberFieldControlled.tsx @@ -1,22 +1,22 @@ "use client"; -import { useHotkeys, useHandlerRef, useElementId } from "@reshaped/headless"; import React from "react"; import Actionable from "@/components/Actionable"; import { useFormControl } from "@/components/FormControl"; import Icon from "@/components/Icon"; import TextField, { TextFieldProps } from "@/components/TextField"; +import useElementId from "@/hooks/useElementId"; +import useHandlerRef from "@/hooks/useHandlerRef"; +import useHotkeys from "@/hooks/useHotkeys"; +import { responsiveClassNames, responsivePropDependency } from "@/utilities/props"; import * as keys from "@/constants/keys"; import IconChevronDown from "@/icons/ChevronDown"; import IconChevronUp from "@/icons/ChevronUp"; import IconMinus from "@/icons/Minus"; import IconPlus from "@/icons/Plus"; -import { responsiveClassNames, responsivePropDependency } from "@/utilities/props"; - -import s from "./NumberField.module.css"; - import type * as T from "./NumberField.types"; +import s from "./NumberField.module.css"; const NumberFieldControlled: React.FC = (props) => { const { diff --git a/packages/reshaped/src/components/NumberField/NumberFieldUncontrolled.tsx b/packages/reshaped/src/components/NumberField/NumberFieldUncontrolled.tsx index af295ddf..7d201b5d 100644 --- a/packages/reshaped/src/components/NumberField/NumberFieldUncontrolled.tsx +++ b/packages/reshaped/src/components/NumberField/NumberFieldUncontrolled.tsx @@ -2,9 +2,8 @@ import React from "react"; -import NumberFieldControlled from "./NumberFieldControlled"; - import type * as T from "./NumberField.types"; +import NumberFieldControlled from "./NumberFieldControlled"; const NumberFieldUncontrolled: React.FC = (props) => { const { defaultValue, onChange } = props; diff --git a/packages/reshaped/src/components/Overlay/Overlay.module.css b/packages/reshaped/src/components/Overlay/Overlay.module.css index 7719f840..46fd3aa7 100644 --- a/packages/reshaped/src/components/Overlay/Overlay.module.css +++ b/packages/reshaped/src/components/Overlay/Overlay.module.css @@ -2,6 +2,7 @@ /* Add an offset in case overlay is rendered inside a scrolled container */ --rs-overlay-offset-y: 0px; --rs-overlay-offset-x: 0px; + --rs-overlay-opacity: 0; position: fixed; overflow-x: clip; @@ -14,13 +15,10 @@ width: 100%; z-index: var(--rs-z-index-fixed); color: var(--rs-color-white); - background-color: rgba(var(--rs-color-rgb-black), 0); + background-color: rgb(from var(--rs-color-black) r g b / 0%); opacity: 0; outline: none; isolation: isolate; - - /* Override default Tailwind reset */ - cursor: default !important; } .wrapper { @@ -42,7 +40,9 @@ } .root.--visible { - background-color: rgba(var(--rs-color-rgb-black), var(--rs-overlay-opacity)); + --rs-overlay-opacity: 1; + + background-color: rgb(from var(--rs-color-black) r g b / calc(var(--rs-overlay-opacity) * 0.7)); opacity: 1; } @@ -84,4 +84,5 @@ .root.--overflow-auto { overflow: auto; + overscroll-behavior: none; } diff --git a/packages/reshaped/src/components/Overlay/Overlay.tsx b/packages/reshaped/src/components/Overlay/Overlay.tsx index d3a95e83..63bbc83d 100644 --- a/packages/reshaped/src/components/Overlay/Overlay.tsx +++ b/packages/reshaped/src/components/Overlay/Overlay.tsx @@ -1,23 +1,19 @@ "use client"; -import { - TrapFocus, - useHotkeys, - useIsomorphicLayoutEffect, - useHandlerRef, - useScrollLock, - useToggle, -} from "@reshaped/headless"; -import { classNames, useIsDismissible } from "@reshaped/headless"; -import { type FocusableElement } from "@reshaped/headless/internal"; import React from "react"; +import { TrapFocus, classNames } from "@reshaped/utilities"; +import { type FocusableElement } from "@reshaped/utilities/internal"; import Portal from "@/components/_private/Portal"; -import { checkTransitions, onNextFrame } from "@/utilities/animation"; - -import s from "./Overlay.module.css"; - +import useHandlerRef from "@/hooks/useHandlerRef"; +import useHotkeys from "@/hooks/useHotkeys"; +import useIsDismissible from "@/hooks/useIsDismissible"; +import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect"; +import useScrollLock from "@/hooks/useScrollLock"; +import useToggle from "@/hooks/useToggle"; +import { onNextFrame, checkTransitions } from "@/utilities/animation"; import type * as T from "./Overlay.types"; +import s from "./Overlay.module.css"; const Overlay: React.FC = (props) => { const { @@ -34,6 +30,7 @@ const Overlay: React.FC = (props) => { containerRef, contained, className, + instanceRef, attributes, } = props; @@ -41,12 +38,12 @@ const Overlay: React.FC = (props) => { const onCloseRef = useHandlerRef(onClose); const onOpenRef = useHandlerRef(onOpen); const onAfterCloseRef = useHandlerRef(onAfterClose); + const onAfterOpenRef = useHandlerRef(onAfterOpen); - const isTransparent = transparent === true; - const opacity = isTransparent ? 0 : (1 - (transparent || 0)) * 0.7; const [mounted, setMounted] = React.useState(false); const [animated, setAnimated] = React.useState(false); const [offset, setOffset] = React.useState([0, 0]); + const rootRef = React.useRef(null); const scopeRef = React.useRef(null); const contentRef = React.useRef(null); const { lockScroll, unlockScroll } = useScrollLock({ containerRef }); @@ -66,7 +63,7 @@ const Overlay: React.FC = (props) => { const rootClassNames = classNames( s.root, visible && s["--visible"], - isTransparent && s["--click-through"], + transparent && s["--click-through"], blurred && s["--blurred"], animated && s["--animated"], shouldBeContained && s["--contained"], @@ -88,7 +85,6 @@ const Overlay: React.FC = (props) => { if (originalOverflowRef.current && containerRef?.current) { containerRef.current.style.overflow = originalOverflowRef.current; - containerRef.current.style.removeProperty("isolation"); originalOverflowRef.current = null; } @@ -103,25 +99,29 @@ const Overlay: React.FC = (props) => { const handleMouseUp = (event: React.MouseEvent) => { const isMouseUpValid = !isInsideContent(event.target as HTMLElement); - const shouldClose = isMouseDownValidRef.current && isMouseUpValid && !isTransparent; + const shouldClose = isMouseDownValidRef.current && isMouseUpValid && !transparent; if (!shouldClose || disableCloseOnClick) return; close({ reason: "overlay-click" }); }; - const handleTransitionEnd = (e: React.TransitionEvent) => { - if (e.propertyName !== "opacity" || e.target !== e.currentTarget) return; + const handleAfterClose = React.useCallback(() => { setAnimated(false); if (visible) { - onAfterOpen?.(); + onAfterOpenRef.current?.(); return; } unlockScroll(); remove(); - onAfterClose?.(); + onAfterCloseRef.current?.(); + }, [visible, onAfterOpenRef, onAfterCloseRef, unlockScroll, remove]); + + const handleTransitionEnd = (e: React.TransitionEvent) => { + if (e.propertyName !== "opacity" || e.target !== e.currentTarget) return; + handleAfterClose(); }; useHotkeys( @@ -137,23 +137,17 @@ const Overlay: React.FC = (props) => { if (hasTransitions) setAnimated(true); if (active && !rendered) render(); if (!active && rendered) { - if (!hasTransitions) { - unlockScroll(); - remove(); - onAfterCloseRef.current?.(); - return; - } - hide(); + if (!hasTransitions) handleAfterClose(); } - }, [active, render, hide, rendered, remove, unlockScroll, onAfterCloseRef]); + }, [active, render, hide, rendered, handleAfterClose]); // Show overlay after it was rendered React.useEffect(() => { if (!rendered) return; - if (!isTransparent) lockScroll(); + if (!transparent) lockScroll(); onNextFrame(() => show()); - }, [rendered, show, lockScroll, isTransparent]); + }, [rendered, show, lockScroll, transparent]); React.useEffect(() => { if (!rendered || !contentRef.current) return; @@ -165,7 +159,6 @@ const Overlay: React.FC = (props) => { originalOverflowRef.current = containerEl.style.overflow; containerEl.style.setProperty("overflow", "hidden"); - containerEl.style.setProperty("isolation", "isolate"); setOffset([containerEl.scrollLeft, containerEl.scrollTop]); } @@ -189,6 +182,17 @@ const Overlay: React.FC = (props) => { setMounted(true); }, []); + React.useImperativeHandle( + instanceRef, + () => ({ + setOpacity: (value: number) => { + if (transparent) return; + rootRef.current?.style.setProperty("--rs-overlay-opacity", `${value}`); + }, + }), + [transparent] + ); + if (!rendered || !mounted) return null; return ( @@ -197,14 +201,18 @@ const Overlay: React.FC = (props) => { {(ref) => (
{ + rootRef.current = node; + ref.current = node; + }} style={ { - "--rs-overlay-opacity": opacity, "--rs-overlay-offset-x": containerRef ? `${offset[0]}px` : undefined, "--rs-overlay-offset-y": containerRef ? `${offset[1]}px` : undefined, + "--rs-overlay-opacity": transparent ? 0 : undefined, } as React.CSSProperties } + // oxlint-disable-next-line jsx_a11y/prefer-tag-over-role role="button" tabIndex={-1} className={rootClassNames} diff --git a/packages/reshaped/src/components/Overlay/Overlay.types.ts b/packages/reshaped/src/components/Overlay/Overlay.types.ts index 39e3cc26..121990cf 100644 --- a/packages/reshaped/src/components/Overlay/Overlay.types.ts +++ b/packages/reshaped/src/components/Overlay/Overlay.types.ts @@ -1,11 +1,17 @@ -import type { Attributes, ClassName } from "@reshaped/headless"; import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + +import type { Attributes } from "@/types/global"; export type CloseReason = "overlay-click" | "escape-key"; +export type Instance = { + setOpacity: (value: number) => void; +} | null; + export type Props = { /** Make the overlay transparent */ - transparent?: boolean | number; + transparent?: boolean; /** Make the overlay blurred */ blurred?: boolean; /** Control the overflow of the component content */ @@ -30,6 +36,8 @@ export type Props = { contained?: boolean; /** Additional classname for the root element */ className?: ClassName; + /** Ref accessor for the overlay methods */ + instanceRef?: React.RefObject; /** Additional attributes for the root element */ attributes?: Attributes<"div">; }; diff --git a/packages/reshaped/src/components/Overlay/index.tsx b/packages/reshaped/src/components/Overlay/index.tsx index 9e0d1df3..6afc4eab 100644 --- a/packages/reshaped/src/components/Overlay/index.tsx +++ b/packages/reshaped/src/components/Overlay/index.tsx @@ -1,2 +1,6 @@ export { default } from "./Overlay"; -export type { Props as OverlayProps, CloseReason as OverlayCloseReason } from "./Overlay.types"; +export type { + Instance as OverlayInstance, + CloseReason as OverlayCloseReason, + Props as OverlayProps, +} from "./Overlay.types"; diff --git a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx index 8ef260b1..f43ba556 100644 --- a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx +++ b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx @@ -1,4 +1,3 @@ -import { useToggle } from "@reshaped/headless"; import { StoryObj } from "@storybook/react-vite"; import React from "react"; import ReactDOM from "react-dom"; @@ -7,6 +6,7 @@ import { expect, fn, Mock, userEvent, waitFor, within } from "storybook/test"; import Button from "@/components/Button"; import Overlay from "@/components/Overlay"; import View from "@/components/View"; +import useToggle from "@/hooks/useToggle"; import { sleep } from "@/utilities/helpers"; import { Example } from "@/utilities/storybook"; @@ -308,6 +308,14 @@ export const testPortal: StoryObj<{ args: { handleClose: fn(), }, + parameters: { + docs: { + source: { + type: "code", + code: [], + }, + }, + }, render: (args) => { const overlayToggle = useToggle(false); diff --git a/packages/reshaped/src/components/Pagination/Pagination.types.ts b/packages/reshaped/src/components/Pagination/Pagination.types.ts index 91c349d5..62c38b50 100644 --- a/packages/reshaped/src/components/Pagination/Pagination.types.ts +++ b/packages/reshaped/src/components/Pagination/Pagination.types.ts @@ -1,4 +1,6 @@ -import type { Attributes, ClassName } from "@reshaped/headless"; +import type { ClassName } from "@reshaped/utilities"; + +import type { Attributes } from "@/types/global"; export type BaseProps = { /** Total number of pages available */ diff --git a/packages/reshaped/src/components/Pagination/PaginationControlled.tsx b/packages/reshaped/src/components/Pagination/PaginationControlled.tsx index d81d5047..aff6fe1c 100644 --- a/packages/reshaped/src/components/Pagination/PaginationControlled.tsx +++ b/packages/reshaped/src/components/Pagination/PaginationControlled.tsx @@ -2,13 +2,11 @@ import Button from "@/components/Button"; import View from "@/components/View"; +import { range } from "@/utilities/helpers"; import IconChevronLeft from "@/icons/ChevronLeft"; import IconChevronRight from "@/icons/ChevronRight"; -import { range } from "@/utilities/helpers"; - -import s from "./Pagination.module.css"; - import type * as T from "./Pagination.types"; +import s from "./Pagination.module.css"; const PaginationControlled: React.FC = (props) => { const { diff --git a/packages/reshaped/src/components/PinField/PinField.module.css b/packages/reshaped/src/components/PinField/PinField.module.css index ba8394b9..7d71347d 100644 --- a/packages/reshaped/src/components/PinField/PinField.module.css +++ b/packages/reshaped/src/components/PinField/PinField.module.css @@ -3,13 +3,6 @@ vertical-align: top; } -.inputWrapper { - position: absolute; - inset: 0; - pointer-events: none; -} - - .input { position: absolute; font-size: 16px; @@ -21,11 +14,10 @@ outline: none; caret-color: transparent; vertical-align: top; - inset-inline-start: 0; - pointer-events: all; - inset-block: 0; - padding-left: 100%; - clip-path: inset(0 calc(50% + var(--rs-unit-x2)) 0 0); + pointer-events: none; + user-select: none; + inset: 0; + opacity: 0; } .item { @@ -34,29 +26,25 @@ } .item--focused { - box-shadow: 0 0 0 1px var(--rs-color-border-primary); - border-color: var(--rs-color-border-primary); - - &:empty::before { - content: ""; - width: 1px; - height: var(--rs-font-size-body-2); - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border-radius: var(--rs-radius-circular); - animation: rs-pin-field-caret 1s ease-out infinite; + box-shadow: 0 0 0 1.5px var(--rs-color-border-primary) inset; - /* Synced with input placeholder color */ - background: var(--rs-color-foreground-neutral); + /* Only show if the value is empty */ + & .caret:last-child { + display: block; } } -@media (hover: hover) { - .root:hover .input { - pointer-events: none; - } +.caret { + width: 1px; + height: var(--rs-font-size-body-2); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: var(--rs-radius-circular); + animation: rs-pin-field-caret 1s ease-out infinite; + display: none; + background: var(--rs-color-foreground-neutral-faded); } @keyframes rs-pin-field-caret { diff --git a/packages/reshaped/src/components/PinField/PinField.tsx b/packages/reshaped/src/components/PinField/PinField.tsx index 3b2529d0..85248a5d 100644 --- a/packages/reshaped/src/components/PinField/PinField.tsx +++ b/packages/reshaped/src/components/PinField/PinField.tsx @@ -1,8 +1,7 @@ +import type * as T from "./PinField.types"; import PinFieldControlled from "./PinFieldControlled"; import PinFieldUncontrolled from "./PinFieldUncontrolled"; -import type * as T from "./PinField.types"; - const PinField: React.FC = (props) => { const { value } = props; diff --git a/packages/reshaped/src/components/PinField/PinField.types.ts b/packages/reshaped/src/components/PinField/PinField.types.ts index 8e8fee0b..9345880e 100644 --- a/packages/reshaped/src/components/PinField/PinField.types.ts +++ b/packages/reshaped/src/components/PinField/PinField.types.ts @@ -1,5 +1,7 @@ +import type { ClassName } from "@reshaped/utilities"; + import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; +import type { Attributes } from "@/types/global"; export type Size = "small" | "medium" | "large" | "xlarge"; diff --git a/packages/reshaped/src/components/PinField/PinFieldControlled.tsx b/packages/reshaped/src/components/PinField/PinFieldControlled.tsx index 2b96eeec..b8e315bf 100644 --- a/packages/reshaped/src/components/PinField/PinFieldControlled.tsx +++ b/packages/reshaped/src/components/PinField/PinFieldControlled.tsx @@ -1,23 +1,21 @@ "use client"; -import { useHotkeys } from "@reshaped/headless"; import React from "react"; import { useFormControl } from "@/components/FormControl"; import Text from "@/components/Text"; import View from "@/components/View"; -import * as keys from "@/constants/keys"; +import useHotkeys from "@/hooks/useHotkeys"; import { onNextFrame } from "@/utilities/animation"; import { responsivePropDependency } from "@/utilities/props"; - +import * as keys from "@/constants/keys"; import { - regExpNumericChar, regExpAlphabeticChar, regExpAlphaNumericChar, + regExpNumericChar, } from "./PinField.constants"; -import s from "./PinField.module.css"; - import type * as T from "./PinField.types"; +import s from "./PinField.module.css"; const sizeMap: Record = { small: 7, @@ -48,10 +46,10 @@ const PinFieldControlled: React.FC = (props) => { const patternRegexp = patternMap[pattern]; const responsiveInputSize = responsivePropDependency(size, (value) => sizeMap[value]); const responsiveTextVariant = responsivePropDependency(size, (value) => - value === "medium" ? "body-3" : "body-2" + value === "medium" ? "body-2" : "body-1" ); const responsiveRadius = responsivePropDependency(size, (value) => - value === "xlarge" ? "medium" : "small" + value === "small" ? "small" : "medium" ); const [focusedIndex, setFocusedIndex] = React.useState(null); const formControl = useFormControl(); @@ -197,7 +195,6 @@ const PinFieldControlled: React.FC = (props) => { const handleItemClick = (event: React.MouseEvent | React.TouchEvent, index: number) => { if (!inputRef.current) return; - event.preventDefault(); inputRef.current.focus(); modeRef.current = index >= value.length ? "type" : "edit"; @@ -211,13 +208,15 @@ const PinFieldControlled: React.FC = (props) => { height={responsiveInputSize} width={responsiveInputSize} borderRadius={responsiveRadius} - borderColor={variant === "faded" ? "transparent" : "neutral"} + borderColor={variant === "faded" ? undefined : "neutral"} + shadow={variant === "faded" ? undefined : "outline"} backgroundColor={variant === "faded" ? "neutral-faded" : "elevation-base"} align="center" justify="center" className={[s.item, focusedIndex === i && s["item--focused"]]} attributes={{ onMouseDown: (e) => { + e.preventDefault(); handleItemClick(e, i); }, onTouchStart: (e) => { @@ -225,6 +224,7 @@ const PinFieldControlled: React.FC = (props) => { }, }} > + {value[i] && {value[i]}} ); @@ -234,25 +234,23 @@ const PinFieldControlled: React.FC = (props) => { {nodes} -
- -
+
); }; diff --git a/packages/reshaped/src/components/PinField/PinFieldUncontrolled.tsx b/packages/reshaped/src/components/PinField/PinFieldUncontrolled.tsx index f86303a6..7bed8ce5 100644 --- a/packages/reshaped/src/components/PinField/PinFieldUncontrolled.tsx +++ b/packages/reshaped/src/components/PinField/PinFieldUncontrolled.tsx @@ -2,9 +2,8 @@ import React from "react"; -import PinFieldControlled from "./PinFieldControlled"; - import type * as T from "./PinField.types"; +import PinFieldControlled from "./PinFieldControlled"; const PinFieldUncontrolled: React.FC = (props) => { const { defaultValue, onChange, ...controlledProps } = props; diff --git a/packages/reshaped/src/components/Popover/Popover.module.css b/packages/reshaped/src/components/Popover/Popover.module.css index 59ead265..514678c9 100644 --- a/packages/reshaped/src/components/Popover/Popover.module.css +++ b/packages/reshaped/src/components/Popover/Popover.module.css @@ -1,28 +1,15 @@ .content { - --rs-border-w: 1px; - max-width: 360px; - border: var(--rs-border-w) solid var(--rs-color-border-neutral-faded); - border-radius: var(--rs-radius-medium); -} - -.content--variant-elevated { - background: var(--rs-color-background-elevation-overlay); color: var(--rs-color-foreground-neutral); - box-shadow: var(--rs-shadow-overlay); min-width: 220px; } -.content--variant-elevated.content--elevation-raised { - box-shadow: var(--rs-shadow-raised); +.content--elevation-raised { + background: var(--rs-color-background-elevation-raised); } -.content--variant-headless { - border: none; -} - -.content--radius-small { - border-radius: var(--rs-radius-small); +.content--elevation-overlay { + background: var(--rs-color-background-elevation-overlay); } .content.content--has-width { diff --git a/packages/reshaped/src/components/Popover/Popover.tsx b/packages/reshaped/src/components/Popover/Popover.tsx index 4453561e..3f961b1f 100644 --- a/packages/reshaped/src/components/Popover/Popover.tsx +++ b/packages/reshaped/src/components/Popover/Popover.tsx @@ -1,35 +1,39 @@ -import { classNames } from "@reshaped/headless"; +import { classNames } from "@reshaped/utilities"; import Dismissible, { type DismissibleProps } from "@/components/Dismissible"; -import Flyout, { useFlyoutContext, type FlyoutProps } from "@/components/Flyout"; +import Flyout, { type FlyoutProps, useFlyoutContext } from "@/components/Flyout"; import { resolveMixin } from "@/styles/mixin"; - -import s from "./Popover.module.css"; - import type * as T from "./Popover.types"; +import s from "./Popover.module.css"; const Popover: React.FC = (props) => { const { width, - variant = "elevated", triggerType = "click", position = "bottom", - elevation, - borderRadius, + elevation = "overlay", + borderRadius = "large", + padding = 4, ...flyoutProps } = props; - const padding = props.padding ?? (variant === "headless" ? 0 : 4); const trapFocusMode = props.trapFocusMode ?? (triggerType === "hover" ? "content-menu" : undefined); - const mixinStyles = resolveMixin({ padding }); + const contentMixinStyles = resolveMixin({ + shadow: elevation, + border: true, + borderColor: "neutral-faded", + radius: borderRadius, + }); + const scrollableMixinStyles = resolveMixin({ + padding, + }); const contentClassName = classNames( s.content, !!width && s["content--has-width"], - variant && s[`content--variant-${variant}`], elevation && s[`content--elevation-${elevation}`], - borderRadius && s[`content--radius-${borderRadius}`], - mixinStyles.classNames + contentMixinStyles.classNames ); + const scrollableClassName = classNames(scrollableMixinStyles.classNames); return ( = (props) => { triggerType={triggerType} width={width} contentClassName={contentClassName} - contentAttributes={{ style: { ...mixinStyles.variables } }} + contentAttributes={{ style: contentMixinStyles.variables }} + scrollableClassName={scrollableClassName} + scrollableAttributes={{ style: scrollableMixinStyles.variables }} /> ); }; diff --git a/packages/reshaped/src/components/Popover/Popover.types.ts b/packages/reshaped/src/components/Popover/Popover.types.ts index a4bc73db..9646646c 100644 --- a/packages/reshaped/src/components/Popover/Popover.types.ts +++ b/packages/reshaped/src/components/Popover/Popover.types.ts @@ -1,16 +1,15 @@ -import type { FlyoutProps, FlyoutInstance } from "@/components/Flyout"; import type React from "react"; +import type { FlyoutInstance, FlyoutProps } from "@/components/Flyout"; + export type Instance = FlyoutInstance; export type Props = Pick< FlyoutProps, | "id" | "position" - | "forcePosition" | "fallbackPositions" | "fallbackAdjustLayout" - | "fallbackMinWidth" | "fallbackMinHeight" | "onOpen" | "onClose" @@ -40,7 +39,5 @@ export type Props = Pick< /** Component elevation level */ elevation?: "raised" | "overlay"; /** Border radius for the content */ - borderRadius?: "small" | "medium"; - /** @deprecated use Flyout utility instead, will be removed in v4 */ - variant?: "elevated" | "headless"; + borderRadius?: "medium" | "large"; }; diff --git a/packages/reshaped/src/components/Popover/index.ts b/packages/reshaped/src/components/Popover/index.ts index 81595667..98ff9a29 100644 --- a/packages/reshaped/src/components/Popover/index.ts +++ b/packages/reshaped/src/components/Popover/index.ts @@ -1,5 +1,4 @@ import Flyout from "@/components/Flyout"; - import Popover, { PopoverDismissible } from "./Popover"; const PopoverRoot = Popover as typeof Popover & { @@ -13,4 +12,4 @@ PopoverRoot.Trigger = Flyout.Trigger; PopoverRoot.Content = Flyout.Content; export default PopoverRoot; -export type { Props as PopoverProps, Instance as PopoverInstance } from "./Popover.types"; +export type { Instance as PopoverInstance, Props as PopoverProps } from "./Popover.types"; diff --git a/packages/reshaped/src/components/Popover/tests/Popover.stories.tsx b/packages/reshaped/src/components/Popover/tests/Popover.stories.tsx index 37e598b0..63166771 100644 --- a/packages/reshaped/src/components/Popover/tests/Popover.stories.tsx +++ b/packages/reshaped/src/components/Popover/tests/Popover.stories.tsx @@ -1,6 +1,6 @@ import { StoryObj } from "@storybook/react-vite"; -import { useState, useId } from "react"; -import { expect, fn, userEvent, within, waitFor } from "storybook/test"; +import { useId, useState } from "react"; +import { expect, fn, userEvent, waitFor, within } from "storybook/test"; import Button from "@/components/Button"; import MenuItem from "@/components/MenuItem"; @@ -276,7 +276,7 @@ export const autoFocus: StoryObj = { render: () => ( - {/* eslint-disable-next-line jsx-a11y/no-autofocus */} + {/* oxlint-disable-next-line jsx_a11y/no-autofocus */} @@ -424,27 +424,3 @@ export const testContentEditable = { ); }, }; - -export const variant = { - name: "variant [deprecated]", - render: () => ( - - - - - {(attributes) => } - - - - - - - - ), -}; diff --git a/packages/reshaped/src/components/Progress/index.ts b/packages/reshaped/src/components/Progress/index.ts deleted file mode 100644 index 808e673d..00000000 --- a/packages/reshaped/src/components/Progress/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./Progress"; -export type { Props as ProgressProps } from "./Progress.types"; diff --git a/packages/reshaped/src/components/Progress/Progress.module.css b/packages/reshaped/src/components/ProgressBar/ProgressBar.module.css similarity index 85% rename from packages/reshaped/src/components/Progress/Progress.module.css rename to packages/reshaped/src/components/ProgressBar/ProgressBar.module.css index 474be8fc..27505a21 100644 --- a/packages/reshaped/src/components/Progress/Progress.module.css +++ b/packages/reshaped/src/components/ProgressBar/ProgressBar.module.css @@ -1,7 +1,7 @@ .root { border-radius: var(--rs-radius-small); overflow: hidden; - background: var(--rs-color-background-neutral-faded); + background: var(--rs-color-background-neutral); display: flex; align-items: flex-start; } @@ -33,6 +33,10 @@ height: var(--rs-unit-x2); } +.--color-neutral .value { + background: var(--rs-color-foreground-neutral-faded); +} + .--color-primary .value { background: var(--rs-color-background-primary); } @@ -50,7 +54,7 @@ } .--color-media { - background: rgba(var(--rs-color-rgb-white), 28%); + background: rgb(from var(--rs-color-white) r g b / 24%); } .--color-media .value { diff --git a/packages/reshaped/src/components/Progress/Progress.tsx b/packages/reshaped/src/components/ProgressBar/ProgressBar.tsx similarity index 79% rename from packages/reshaped/src/components/Progress/Progress.tsx rename to packages/reshaped/src/components/ProgressBar/ProgressBar.tsx index 4688d76c..e00306ce 100644 --- a/packages/reshaped/src/components/Progress/Progress.tsx +++ b/packages/reshaped/src/components/ProgressBar/ProgressBar.tsx @@ -1,11 +1,10 @@ -import { classNames } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; -import s from "./Progress.module.css"; +import type * as T from "./ProgressBar.types"; +import s from "./ProgressBar.module.css"; -import type * as T from "./Progress.types"; - -const Progress: React.FC = (props) => { +const ProgressBar: React.FC = (props) => { const { value = 0, min = 0, @@ -53,6 +52,6 @@ const Progress: React.FC = (props) => { ); }; -Progress.displayName = "Progress"; +ProgressBar.displayName = "ProgressBar"; -export default Progress; +export default ProgressBar; diff --git a/packages/reshaped/src/components/Progress/Progress.types.ts b/packages/reshaped/src/components/ProgressBar/ProgressBar.types.ts similarity index 85% rename from packages/reshaped/src/components/Progress/Progress.types.ts rename to packages/reshaped/src/components/ProgressBar/ProgressBar.types.ts index 6f5e7bf3..03bbbb46 100644 --- a/packages/reshaped/src/components/Progress/Progress.types.ts +++ b/packages/reshaped/src/components/ProgressBar/ProgressBar.types.ts @@ -1,4 +1,6 @@ -import type { Attributes, ClassName } from "@reshaped/headless"; +import type { ClassName } from "@reshaped/utilities"; + +import type { Attributes } from "@/types/global"; export type Props = { /** Value of the progress bar controlling the size of the fill */ @@ -10,7 +12,7 @@ export type Props = { /** Component size */ size?: "small" | "medium"; /** Component color scheme */ - color?: "primary" | "critical" | "warning" | "positive" | "media"; + color?: "primary" | "critical" | "warning" | "positive" | "media" | "neutral"; /** Duration of the progress bar animation in milliseconds */ duration?: number; /** aria-label attribute for the root element */ diff --git a/packages/reshaped/src/components/ProgressBar/index.ts b/packages/reshaped/src/components/ProgressBar/index.ts new file mode 100644 index 00000000..9ee6851c --- /dev/null +++ b/packages/reshaped/src/components/ProgressBar/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ProgressBar"; +export type { Props as ProgressBarProps } from "./ProgressBar.types"; diff --git a/packages/reshaped/src/components/Progress/tests/Progress.stories.tsx b/packages/reshaped/src/components/ProgressBar/tests/ProgressBar.stories.tsx similarity index 66% rename from packages/reshaped/src/components/Progress/tests/Progress.stories.tsx rename to packages/reshaped/src/components/ProgressBar/tests/ProgressBar.stories.tsx index 0887676b..6ce9822f 100644 --- a/packages/reshaped/src/components/Progress/tests/Progress.stories.tsx +++ b/packages/reshaped/src/components/ProgressBar/tests/ProgressBar.stories.tsx @@ -3,16 +3,16 @@ import React from "react"; import { expect } from "storybook/test"; import Button from "@/components/Button"; -import Progress from "@/components/Progress"; +import ProgressBar from "@/components/ProgressBar"; import View from "@/components/View"; import { Example } from "@/utilities/storybook"; export default { - title: "Components/Progress", - component: Progress, + title: "Components/ProgressBar", + component: ProgressBar, parameters: { iframe: { - url: "https://reshaped.so/docs/components/progress", + url: "https://reshaped.so/docs/components/progress-bar", }, }, }; @@ -22,13 +22,13 @@ export const value = { render: () => ( - + - + - + ), @@ -39,10 +39,10 @@ export const size = { render: () => ( - + - + ), @@ -52,18 +52,21 @@ export const color = { name: "color", render: () => ( + + + - + - + - + - + @@ -87,7 +90,7 @@ export const duration = { - + @@ -97,7 +100,7 @@ export const duration = { export const render: StoryObj = { name: "rendering", - render: () => , + render: () => , play: async ({ canvas }) => { const el = canvas.getByRole("progressbar"); @@ -113,7 +116,7 @@ export const className: StoryObj = { name: "className, attributes", render: () => (
- +
), play: async ({ canvas }) => { diff --git a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.module.css b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.module.css index c6521022..54ff1d2e 100644 --- a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.module.css +++ b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.module.css @@ -7,10 +7,10 @@ box-sizing: initial; mask-image: linear-gradient( to right, - rgba(0, 0, 0, 0%) 0%, - rgba(0, 0, 0, 100%) var(--rs-unit-x4), - rgba(0, 0, 0, 100%) calc(100% - var(--rs-unit-x4)), - rgba(0, 0, 0, 0%) 100% + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 100%) var(--rs-unit-x4), + rgb(0 0 0 / 100%) calc(100% - var(--rs-unit-x4)), + rgb(0 0 0 / 0%) 100% ); } @@ -19,7 +19,7 @@ vertical-align: top; position: relative; transform: translateX(calc(var(--rs-unit-x4) * -1 * var(--rs-progress-indicator-mod))); - transition: var(--rs-duration-slow) var(--rs-easing-decelerate); + transition: var(--rs-duration-medium) var(--rs-easing-decelerate); transition-property: transform; } @@ -30,7 +30,7 @@ border-radius: var(--rs-radius-circular); margin-inline-start: var(--rs-unit-x2); transform: translateZ(0); - transition: var(--rs-duration-slow) var(--rs-easing-decelerate); + transition: var(--rs-duration-medium) var(--rs-easing-decelerate); transition-property: transform, opacity, background-color, width; &:first-child { @@ -61,4 +61,3 @@ } } } - diff --git a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx index d9d289d7..7c94308b 100644 --- a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx +++ b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx @@ -1,11 +1,10 @@ "use client"; -import { classNames } from "@reshaped/headless"; import React from "react"; - -import s from "./ProgressIndicator.module.css"; +import { classNames } from "@reshaped/utilities"; import type * as T from "./ProgressIndicator.types"; +import s from "./ProgressIndicator.module.css"; const VISIBLE_ITEMS = 7; const HALF_ITEMS = Math.floor(VISIBLE_ITEMS / 2); diff --git a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.types.ts b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.types.ts index 826a1d3c..9d5e92d6 100644 --- a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.types.ts +++ b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.types.ts @@ -1,4 +1,6 @@ -import type { Attributes, ClassName } from "@reshaped/headless"; +import type { ClassName } from "@reshaped/utilities"; + +import type { Attributes } from "@/types/global"; export type Props = { /** Total amount of progress indicator dots */ diff --git a/packages/reshaped/src/components/ProgressIndicator/tests/ProgressIndicator.stories.tsx b/packages/reshaped/src/components/ProgressIndicator/tests/ProgressIndicator.stories.tsx index ca0be467..171f25a9 100644 --- a/packages/reshaped/src/components/ProgressIndicator/tests/ProgressIndicator.stories.tsx +++ b/packages/reshaped/src/components/ProgressIndicator/tests/ProgressIndicator.stories.tsx @@ -4,7 +4,6 @@ import { expect } from "storybook/test"; import Button from "@/components/Button"; import ProgressIndicator from "@/components/ProgressIndicator"; -import Scrim from "@/components/Scrim"; import Text from "@/components/Text"; import View from "@/components/View"; import { Example } from "@/utilities/storybook"; @@ -47,15 +46,23 @@ export const base: StoryObj = { Index: {activeIndex} - - } + + - - - - + + @@ -73,15 +80,23 @@ export const color = { - - } + + - - - - + + diff --git a/packages/reshaped/src/components/Radio/Radio.module.css b/packages/reshaped/src/components/Radio/Radio.module.css index 2838a4cc..934ff45b 100644 --- a/packages/reshaped/src/components/Radio/Radio.module.css +++ b/packages/reshaped/src/components/Radio/Radio.module.css @@ -52,27 +52,36 @@ since it reset the border width/color without using @layer transition: var(--rs-duration-fast) var(--rs-easing-standard); transition-property: opacity, transform; } + + [data-rs-keyboard] & { + transition: none; + + &::after { + transition: none; + } + } } @responsive .--size { @value small { --rs-radio-line-height: var(--rs-line-height-caption-1); - --rs-radio-gap: var(--rs-unit-x1); + --rs-radio-gap: var(--rs-unit-x1-5); } @value medium { - --rs-radio-line-height: var(--rs-line-height-body-3); + --rs-radio-line-height: var(--rs-line-height-body-2); --rs-radio-gap: var(--rs-unit-x2); } @value large { - --rs-radio-line-height: var(--rs-line-height-body-2); + --rs-radio-line-height: var(--rs-line-height-body-1); --rs-radio-gap: var(--rs-unit-x2); } } [data-rs-keyboard] .input:focus + .decorator { - box-shadow: var(--rs-shadow-focus); + outline: var(--rs-outline); + outline-offset: var(--rs-outline-width); } /* } */ @@ -80,6 +89,7 @@ since it reset the border width/color without using @layer /* @layer rs.radio.error { */ .root.--error .decorator { border-color: var(--rs-color-border-critical); + border-width: var(--rs-outline-width); } /* } */ diff --git a/packages/reshaped/src/components/Radio/Radio.tsx b/packages/reshaped/src/components/Radio/Radio.tsx index a2a533d6..a57fda02 100644 --- a/packages/reshaped/src/components/Radio/Radio.tsx +++ b/packages/reshaped/src/components/Radio/Radio.tsx @@ -1,25 +1,21 @@ "use client"; -import { classNames } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; import { useFormControl } from "@/components/FormControl"; import HiddenInput from "@/components/HiddenInput"; import { useRadioGroup } from "@/components/RadioGroup"; import Text from "@/components/Text"; import { responsiveClassNames, responsivePropDependency } from "@/utilities/props"; - -import s from "./Radio.module.css"; - import type * as T from "./Radio.types"; +import s from "./Radio.module.css"; const Radio: React.FC = (props) => { const { children, value, onChange, - onFocus, - onBlur, size = "medium", className, attributes, @@ -52,8 +48,6 @@ const Radio: React.FC = (props) => { disabled={disabled} value={value} onChange={onChange} - onFocus={onFocus} - onBlur={onBlur} attributes={inputAttributes} />
@@ -63,9 +57,9 @@ const Radio: React.FC = (props) => { { - if (size === "large") return "body-2"; + if (size === "large") return "body-1"; if (size === "small") return "caption-1"; - return "body-3"; + return "body-2"; })} > {children} diff --git a/packages/reshaped/src/components/Radio/Radio.types.ts b/packages/reshaped/src/components/Radio/Radio.types.ts index 777858c0..e954f40e 100644 --- a/packages/reshaped/src/components/Radio/Radio.types.ts +++ b/packages/reshaped/src/components/Radio/Radio.types.ts @@ -1,6 +1,8 @@ -import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + +import type * as G from "@/types/global"; +import type { Attributes } from "@/types/global"; type BaseProps = { /** Node for inserting children */ @@ -17,10 +19,6 @@ type BaseProps = { size?: G.Responsive<"small" | "medium" | "large">; /** Callback when the input value changes */ onChange?: G.ChangeHandler; - /** Callback when the input is focused */ - onFocus?: (e: React.FocusEvent) => void; - /** Callback when the input is blurred */ - onBlur?: (e: React.FocusEvent) => void; /** Additional classname for the root element */ className?: ClassName; /** Additional attributes for the root element */ diff --git a/packages/reshaped/src/components/RadioGroup/RadioGroup.tsx b/packages/reshaped/src/components/RadioGroup/RadioGroup.tsx index 873b4bdb..96d307f0 100644 --- a/packages/reshaped/src/components/RadioGroup/RadioGroup.tsx +++ b/packages/reshaped/src/components/RadioGroup/RadioGroup.tsx @@ -1,8 +1,7 @@ +import type * as T from "./RadioGroup.types"; import RadioGroupControlled from "./RadioGroupControlled"; import RadioGroupUncontrolled from "./RadioGroupUncontrolled"; -import type * as T from "./RadioGroup.types"; - const RadioGroup: React.FC = (props) => { const { value } = props; diff --git a/packages/reshaped/src/components/RadioGroup/RadioGroup.types.ts b/packages/reshaped/src/components/RadioGroup/RadioGroup.types.ts index 5a2b6807..cd6577ee 100644 --- a/packages/reshaped/src/components/RadioGroup/RadioGroup.types.ts +++ b/packages/reshaped/src/components/RadioGroup/RadioGroup.types.ts @@ -1,7 +1,9 @@ +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type { RadioProps } from "@/components/Radio"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; type BaseProps = { /** Unique identifier for the radio group */ diff --git a/packages/reshaped/src/components/RadioGroup/RadioGroupControlled.tsx b/packages/reshaped/src/components/RadioGroup/RadioGroupControlled.tsx index 54cf8311..b6aaef58 100644 --- a/packages/reshaped/src/components/RadioGroup/RadioGroupControlled.tsx +++ b/packages/reshaped/src/components/RadioGroup/RadioGroupControlled.tsx @@ -1,9 +1,8 @@ "use client"; +import type * as TRadio from "@/components/Radio/Radio.types"; import Context from "./RadioGroup.context"; - import type * as T from "./RadioGroup.types"; -import type * as TRadio from "@/components/Radio/Radio.types"; const RadioGroupControlled: React.FC = (props) => { const { onChange, name, disabled, value, children, hasError } = props; diff --git a/packages/reshaped/src/components/RadioGroup/RadioGroupUncontrolled.tsx b/packages/reshaped/src/components/RadioGroup/RadioGroupUncontrolled.tsx index 94b5757c..356dd4f9 100644 --- a/packages/reshaped/src/components/RadioGroup/RadioGroupUncontrolled.tsx +++ b/packages/reshaped/src/components/RadioGroup/RadioGroupUncontrolled.tsx @@ -2,9 +2,8 @@ import React from "react"; -import RadioGroupControlled from "./RadioGroupControlled"; - import type * as T from "./RadioGroup.types"; +import RadioGroupControlled from "./RadioGroupControlled"; const RadioGroupUncontrolled: React.FC = (props) => { const { defaultValue, onChange } = props; diff --git a/packages/reshaped/src/components/Reshaped/Reshaped.css b/packages/reshaped/src/components/Reshaped/Reshaped.css index b992f9c9..4db62359 100644 --- a/packages/reshaped/src/components/Reshaped/Reshaped.css +++ b/packages/reshaped/src/components/Reshaped/Reshaped.css @@ -1,11 +1,9 @@ @layer rs.reset { [data-rs-theme] { + --rs-outline-width: 1.5px; + --rs-outline: var(--rs-outline-width) solid var(--rs-color-border-primary); + --rs-outline-shadow: 0 0 0 var(--rs-outline-width) var(--rs-color-border-primary) inset; --rs-radius-circular: 9999px; - --rs-shadow-focus: - 0 0 0 2px var(--rs-color-background-elevation-base), 0 0 0 4px var(--rs-color-border-primary); - --rs-shadow-focus-inset: - inset 0 0 0 2px var(--rs-color-border-primary), - inset 0 0 0 4px var(--rs-color-background-elevation-base); } [data-rs-theme] body, @@ -106,14 +104,19 @@ [data-rs-theme] body, [data-rs-theme]:not(html) { - font-size: var(--rs-font-size-body-3); - line-height: var(--rs-line-height-body-3); - letter-spacing: var(--rs-letter-spacing-body-3); + font-size: var(--rs-font-size-body-2); + line-height: var(--rs-line-height-body-2); + letter-spacing: var(--rs-letter-spacing-body-2); font-family: var(--rs-font-family-body); font-weight: var(--rs-font-weight-regular); } } +[data-rs-theme] { + /* Reset for scoped themes */ + color: var(--rs-color-foreground-neutral); +} + [data-rs-color-mode="light"] { color-scheme: light; } diff --git a/packages/reshaped/src/components/Reshaped/Reshaped.tsx b/packages/reshaped/src/components/Reshaped/Reshaped.tsx index 1c836e7c..35c1ed28 100644 --- a/packages/reshaped/src/components/Reshaped/Reshaped.tsx +++ b/packages/reshaped/src/components/Reshaped/Reshaped.tsx @@ -1,27 +1,27 @@ "use client"; -import { Reshaped as HeadlessReshaped, classNames } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; import { GlobalColorMode, PrivateTheme } from "@/components/Theme"; import { useGlobalColorMode } from "@/components/Theme/useTheme"; import { ToastProvider } from "@/components/Toast"; -import { SingletonViewportProvider } from "@/hooks/_private/useSingletonViewport"; - -import s from "./Reshaped.module.css"; - +import { SingletonHotkeysProvider } from "@/hooks/_internal/useSingletonHotkeys"; +import { SingletonKeyboardModeProvider } from "@/hooks/_internal/useSingletonKeyboardMode"; +import { SingletonRTLProvider } from "@/hooks/_internal/useSingletonRTL"; +import { SingletonViewportProvider } from "@/hooks/_internal/useSingletonViewport"; import type * as T from "./Reshaped.types"; - import "./Reshaped.css"; +import s from "./Reshaped.module.css"; const Reshaped: React.FC = (props) => { const { theme, - defaultTheme = "reshaped", + defaultTheme = "slate", colorMode, defaultColorMode, defaultViewport, - toastOptions, + defaultRTL, scoped, className, } = props; @@ -30,25 +30,29 @@ const Reshaped: React.FC = (props) => { const parentGlobalColorMode = useGlobalColorMode(); return ( - - - - - {props.children} - - - - + + + + + + + {props.children} + + + + + + ); }; diff --git a/packages/reshaped/src/components/Reshaped/Reshaped.types.ts b/packages/reshaped/src/components/Reshaped/Reshaped.types.ts index 56d9e528..61ebc9a2 100644 --- a/packages/reshaped/src/components/Reshaped/Reshaped.types.ts +++ b/packages/reshaped/src/components/Reshaped/Reshaped.types.ts @@ -1,8 +1,9 @@ +import type React from "react"; +import type { ClassName } from "@reshaped/utilities"; + import type { GlobalColorModeProps, ThemeProps } from "@/components/Theme"; -import type { ToastProviderProps } from "@/components/Toast"; import type * as G from "@/types/global"; -import type { Attributes, ClassName } from "@reshaped/headless"; -import type React from "react"; +import type { Attributes } from "@/types/global"; export type Props = { /** Node for inserting children */ @@ -19,8 +20,6 @@ export type Props = { defaultColorMode?: GlobalColorModeProps["defaultMode"]; /** Default viewport size of the application */ defaultViewport?: G.Viewport; - /** Global options for the ToastProvider */ - toastOptions?: ToastProviderProps["options"]; /** Enable scoped mode for applications not using Reshaped provider at the application root */ scoped?: boolean; /** Additional classname for the root element */ diff --git a/packages/reshaped/src/components/Reshaped/tests/Reshaped.stories.tsx b/packages/reshaped/src/components/Reshaped/tests/Reshaped.stories.tsx index 38b1f145..f7e9095d 100644 --- a/packages/reshaped/src/components/Reshaped/tests/Reshaped.stories.tsx +++ b/packages/reshaped/src/components/Reshaped/tests/Reshaped.stories.tsx @@ -4,10 +4,8 @@ import { expect, userEvent } from "storybook/test"; import Button from "@/components/Button"; import { useTheme } from "@/components/Theme"; - -import Reshaped from "../Reshaped"; - import type * as G from "@/types/global"; +import Reshaped from "../Reshaped"; export default { title: "Utility components/Reshaped", @@ -19,7 +17,7 @@ export default { export const rtl = { name: "defaultRTL", render: () => ( - + Hello ), @@ -31,7 +29,7 @@ export const controlledMode: StoryObj = { const [mode, setMode] = useState("dark"); return ( - + - - - - ); - - return ( - - 0} - title="Step 1" - subtitle={props.subtitle || "Step subtitle"} - > - {content} - - 1} title="Step 2"> - {content} - - 2} title="Step 3 very long title"> - {content} - - - ); -}; - -export const direction = { - name: "direction", - render: () => ( - - - - - - - - - - - - - - - - - ), -}; - -export const labelDisplay = { - name: "labelDisplay", - render: () => ( - - - - - - - - - - - - - - - - - - - - - - - - ), -}; - -export const gap = { - name: "gap", - render: () => ( - - - - - - - - - - - - - - - - - - - - - - - - ), -}; - -export const className: StoryObj = { - name: "className, attributes", - render: () => ( -
- - - -
- ), - play: async ({ canvas }) => { - const root = canvas.getByTestId("root").firstChild; - const item = root?.firstChild; - - expect(root).toHaveClass("test-classname"); - expect(root).toHaveAttribute("id", "test-id"); - - expect(item).toHaveClass("test-item-classname"); - expect(item).toHaveAttribute("id", "test-item-id"); - }, -}; - -export const edgeCases = { - name: "test: edge cases", - render: () => ( - - - - - - ), -}; diff --git a/packages/reshaped/src/components/Switch/Switch.module.css b/packages/reshaped/src/components/Switch/Switch.module.css index 88465cd6..91c8a7f6 100644 --- a/packages/reshaped/src/components/Switch/Switch.module.css +++ b/packages/reshaped/src/components/Switch/Switch.module.css @@ -37,7 +37,8 @@ } [data-rs-keyboard] &:focus + .area { - box-shadow: var(--rs-shadow-focus); + outline: var(--rs-outline); + outline-offset: var(--rs-outline-width); } &[disabled] + .area { @@ -95,7 +96,7 @@ @value small { --rs-switch-height: var(--rs-unit-x4); --rs-switch-width: var(--rs-unit-x6); - --rs-switch-gap: var(--rs-unit-x1); + --rs-switch-gap: var(--rs-unit-x1-5); --rs-switch-line-height: var(--rs-line-height-caption-1); --rs-switch-font-size: var(--rs-font-size-caption-1); } @@ -104,15 +105,15 @@ --rs-switch-height: var(--rs-unit-x5); --rs-switch-width: var(--rs-unit-x8); --rs-switch-gap: var(--rs-unit-x2); - --rs-switch-line-height: var(--rs-line-height-body-3); - --rs-switch-font-size: var(--rs-font-size-body-3); + --rs-switch-line-height: var(--rs-line-height-body-2); + --rs-switch-font-size: var(--rs-font-size-body-2); } @value large { --rs-switch-height: var(--rs-unit-x6); --rs-switch-width: var(--rs-unit-x10); --rs-switch-gap: var(--rs-unit-x2); - --rs-switch-line-height: var(--rs-line-height-body-2); - --rs-switch-font-size: var(--rs-font-size-body-2); + --rs-switch-line-height: var(--rs-line-height-body-1); + --rs-switch-font-size: var(--rs-font-size-body-1); } } diff --git a/packages/reshaped/src/components/Switch/Switch.tsx b/packages/reshaped/src/components/Switch/Switch.tsx index 77806097..24b5240d 100644 --- a/packages/reshaped/src/components/Switch/Switch.tsx +++ b/packages/reshaped/src/components/Switch/Switch.tsx @@ -1,15 +1,14 @@ "use client"; -import { classNames, useElementId } from "@reshaped/headless"; import React from "react"; +import { classNames } from "@reshaped/utilities"; import { useFormControl } from "@/components/FormControl"; import Text from "@/components/Text"; +import useElementId from "@/hooks/useElementId"; import { responsiveClassNames, responsivePropDependency } from "@/utilities/props"; - -import s from "./Switch.module.css"; - import type * as T from "./Switch.types"; +import s from "./Switch.module.css"; const Switch: React.FC = (props) => { const { @@ -20,8 +19,6 @@ const Switch: React.FC = (props) => { reversed, defaultChecked, onChange, - onFocus, - onBlur, className, attributes, } = props; @@ -58,8 +55,6 @@ const Switch: React.FC = (props) => { defaultChecked={defaultChecked} disabled={disabled} onChange={handleChange} - onFocus={onFocus || inputAttributes?.onFocus} - onBlur={onBlur || inputAttributes?.onBlur} id={id} />