-
Notifications
You must be signed in to change notification settings - Fork 808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[added] Support using multiple document.body classes #452
[added] Support using multiple document.body classes #452
Conversation
CONTRIBUTING.md
Outdated
@@ -30,8 +30,6 @@ always be in sync. | |||
|
|||
- `npm start` runs the dev server to run/develop examples | |||
- `npm test` will run the tests. | |||
- `scripts/test` same as `npm test` but keeps karma running and watches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this line because it seems like it's obsolete. When I run npm test
, I karma keeps running and watches my changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be moved to another PR.
simultaneously). | ||
|
||
`bodyOpenClassName` can support adding multiple classes to `document.body` when | ||
the modal is open. Add as many class names as you desire, delineated by spaces. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section
bodyOpenClassName
can support adding multiple classes todocument.body
when
the modal is open. Add as many class names as you desire, delineated by spaces.
is the only newly added documentation. The rest of the changes in this file were simply refactors of the existing documentation into a more easily editable form (e.g. line breaks at ~80 characters, newlines between separate paragraphs, removal of duplicate spacing, etc.)
If you'd prefer I undo these changes and merely add my documentation, just let me know.
1 similar comment
Hi @indiesquidge, the reason for Initially, we made it a single constant class name, so we don't end up with a complex system to manage these classes on the To accept this PR, this is the property it must hold:
If it fails on this, one shared class might disappear and it would be hard to find why. The same result can be achieved using |
CONTRIBUTING.md
Outdated
@@ -30,8 +30,6 @@ always be in sync. | |||
|
|||
- `npm start` runs the dev server to run/develop examples | |||
- `npm test` will run the tests. | |||
- `scripts/test` same as `npm test` but keeps karma running and watches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be moved to another PR.
src/components/ModalPortal.js
Outdated
elementClass(document.body).remove(bodyOpenClassName); | ||
bodyOpenClassName.split(' ').forEach(className => { | ||
document.body.classList.remove(className); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might remove a shared class if more than one modal is opened.
@diasbruno, thanks for the tips! I have updated my code to handle the test case you laid out about modals using shared classes.
This makes sense for sure. I'm just hoping to be able to add multiple classes to
I definitely agree that a complex system to manage the classes would be bad. I see that
Using these hooks is what I initially looked at doing, but I'm already doing a number of other things in each of the methods and figured having the ability to add multiple classes via the |
You can remove the artifacts ( |
Tests seem to be breaking on Node 6 & 7, but it works fine on my local machine (Node 8). Not sure what the issue is; I'll look into it later. |
Strange, even when I switch my node version to 6.11.0, all tests still pass on my machine... |
Yeah, it seems to be working (I've download your branch and run the tests on |
specs/Modal.spec.js
Outdated
expect(isBodyWithReactModalOpenClass('A B')).toBe(false); | ||
expect(isBodyWithReactModalOpenClass('A')).toBe(true); | ||
unmountModal(); | ||
expect(isBodyWithReactModalOpenClass('A')).toBe(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expectations should always be true.
expect(!isBodyWithReactModalOpenClass('A B')).toBeTruth();
expect(isBodyWithReactModalOpenClass('A')).toBeTruthy();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I can certainly change the the tests to use the toBeTruthy()
method, but may I ask why it is preferred?
While working with these tests, the terseness of using toBeTruthy()
on bang methods methods—instead of something like toBe(false)
—makes it more difficult to assess what one is asserting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to follow the current code style.
My opinion: I'd like to see something like expect(!something_that_is_false);
, because every expectation ends up been a true statement.
F = F => T
src/components/ModalPortal.js
Outdated
bodyOpenClassName | ||
.split(' ') | ||
.map(refCount.add) | ||
.forEach(bodyClass => document.body.classList.add(bodyClass)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make this pipeline a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this okay?
addClassToBody = (bodyClass) => {
// Increment class(es) on refCount tracker and add class(es) to body
bodyClass
.split(' ')
.map(refCount.add)
.forEach(className => document.body.classList.add(className));
}
src/components/ModalPortal.js
Outdated
.split(' ') | ||
.map(refCount.remove) | ||
.filter(bodyClass => classListMap[bodyClass] === 0) | ||
.forEach(bodyClass => document.body.classList.remove(bodyClass)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make this pipeline a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this okay?
removeClassFromBody = (bodyClass) => {
const classListMap = refCount.get();
// Decrement class(es) from the refCount tracker
// and remove unused class(es) from body
bodyClass
.split(' ')
.map(refCount.remove)
.filter(className => classListMap[className] === 0)
.forEach(className => document.body.classList.remove(className));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can create a new file on helpers folder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet! Any preference on the name? I was thinking something like bodyClassManager
or bodyElementManager
, but am definitely open to other suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe remove
and add
are good enough, so we use with namespace.
// classname can be changed.
import * as classname from '../helpers/classname';
classname.add(...);
classname.remove(...);
// Add body class | ||
elementClass(document.body).add(bodyOpenClassName); | ||
bodyClassList.add(bodyOpenClassName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this is iterated upon, it seems like we are moving in a direction very similar to the elementClass
library that was here before. (The major under-the-hood difference being that bodyOpenClassName
can be a string of multiple class names, and using the other helper refCount
to keep track of opened modals relying on the class internally.)
If you would prefer, I could make bodyClassList
look and feel similar to elementClass
, i.e. have document.body
be passed in as an argument like it is here. This would remove the line change here, thus keeping the "public API" of the helper class the same as how elementClass
looked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've created a commit on another branch to demonstrate what this might look like.
I honestly prefer it to the more restrictive bodyClassList
helper which is directly bound to the body element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@diasbruno, which implementation would you prefer? Does the thumbs up mean you think I should implement the code outlined in the commit I linked to? I think the code in the external commit is a more robust solution than what currently exists in this PR, but it's up to you :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ow sorry. the version that uses document.body
directly. :)
@diasbruno, is there any way to simulate this test run locally? Setting my node version to the same still passes for me locally; running the exact command Travis does (i.e. Basically anything I can think of to try and "break" the test script locally by mimicking the Travis build is not working—the tests always seem to behave as I would expect when I run them locally. |
I think this PR is ready. You can try installing Firefox 38 and run |
ping @ajfuller. Any thoughts? |
Code looks nice to me 👍 I don't see anything that stands out as to why it's failing on Firefox 38 either. I'll try to pull down and test later, but here is the link to download Firefox 38: |
@indiesquidge I think you can push a commit with |
* update `bodyOpenClassName` prop to handle adding and removing multiple class names * update String#includes polyfill to work properly * ensure shared classes on `document.body` persist on one modal close if multiple modals are open * create new helper for adding/removing class names from body element * remove unmaintained and obsolete `element-class` library * rename refCount private variable `modals` to `classListMap` * create `get` method on refCount helper for public access to the class list count
@diasbruno, alright I finally got the test failing locally (thanks, @ajfuller for the link to Firefox 38). I narrowed the issue down to String#includes. Firefox 38 does not have this function natively, so it relies on the polyfill provided in The polyfill breaks on other tests that should return true String.prototype.includes = function(item) {
return this.length > 0 && this.split(" ").indexOf(item) !== -1;
}
const str = 'To be, or not to be, that is the question.'
console.log(str.includes('To be')) // returns false; should be true I have updated the polyfill to use the MDN polyfill, which seems to work properly and indeed the test passes after this change. |
1 similar comment
@indiesquidge This is great! Sorry for the long code review and thanks for the effort on this feature. |
I'm merging this PR. Maybe later, we can think on how to remove the polyfill. Maybe we can run those tests with |
Thanks @diasbruno for speedy replies and for helping me along with this PR. Glad I could help :) |
Released |
Fixes #451.
Changes proposed:
bodyOpenClassName
prop to handle adding and removing multiple class nameselement-class
libraryUse Case
I am using the Tachyons library for CSS styling in my app. If you have not worked with Tachyons before (it's wonderful), it is essentially a collection of atomic class names—i.e., class names that do only one thing (e.g.
.fixed { position: fixed; }
). Because of this, it is in the nature of Tachyons that multiple classes will be tacked on to a single element to achieve a desired style.Recently, I ran into the issue outlined in #369. To solve the issue, I need to apply two CSS properties to the
document.body
element when the modal opens:position: fixed
andoverflow: hidden
.To do this with Tachyons, I need to have
bodyOpenClassName='fixed overflow-hidden'
.However, this causes a bug in the current version of the
react-modal
library in which the classes are appended ad infinitum (see here).Acceptance Checklist:
CONTRIBUTING.md
.